@sagmans/dsh-tui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -6
- package/cordis.patch.yml +14 -0
- package/lib/agent/projections.d.ts +22 -6
- package/lib/agent/projections.d.ts.map +1 -1
- package/lib/agent/projections.js +21 -10
- package/lib/agent/projections.js.map +1 -1
- package/lib/agent/prompt-history.d.ts +89 -0
- package/lib/agent/prompt-history.d.ts.map +1 -0
- package/lib/agent/prompt-history.js +380 -0
- package/lib/agent/prompt-history.js.map +1 -0
- package/lib/agent/status.d.ts +6 -0
- package/lib/agent/status.d.ts.map +1 -1
- package/lib/agent/status.js +1 -0
- package/lib/agent/status.js.map +1 -1
- package/lib/herdr/client.d.ts +57 -0
- package/lib/herdr/client.d.ts.map +1 -0
- package/lib/herdr/client.js +218 -0
- package/lib/herdr/client.js.map +1 -0
- package/lib/herdr/constants.d.ts +93 -0
- package/lib/herdr/constants.d.ts.map +1 -0
- package/lib/herdr/constants.js +79 -0
- package/lib/herdr/constants.js.map +1 -0
- package/lib/herdr/reporter.d.ts +60 -0
- package/lib/herdr/reporter.d.ts.map +1 -0
- package/lib/herdr/reporter.js +217 -0
- package/lib/herdr/reporter.js.map +1 -0
- package/lib/herdr/state.d.ts +57 -0
- package/lib/herdr/state.d.ts.map +1 -0
- package/lib/herdr/state.js +62 -0
- package/lib/herdr/state.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +252 -20
- package/lib/index.js.map +1 -1
- package/lib/input/actions.d.ts +1 -1
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +3 -0
- package/lib/input/actions.js.map +1 -1
- package/lib/input/ghost.d.ts +46 -0
- package/lib/input/ghost.d.ts.map +1 -0
- package/lib/input/ghost.js +58 -0
- package/lib/input/ghost.js.map +1 -0
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +2 -0
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/submission.d.ts +24 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +28 -1
- package/lib/input/submission.js.map +1 -1
- package/lib/stash/lock.d.ts +72 -0
- package/lib/stash/lock.d.ts.map +1 -0
- package/lib/stash/lock.js +374 -0
- package/lib/stash/lock.js.map +1 -0
- package/lib/stash/paths.d.ts +22 -0
- package/lib/stash/paths.d.ts.map +1 -0
- package/lib/stash/paths.js +83 -0
- package/lib/stash/paths.js.map +1 -0
- package/lib/stash/private-fs.d.ts +61 -0
- package/lib/stash/private-fs.d.ts.map +1 -0
- package/lib/stash/private-fs.js +379 -0
- package/lib/stash/private-fs.js.map +1 -0
- package/lib/stash/schema.d.ts +57 -0
- package/lib/stash/schema.d.ts.map +1 -0
- package/lib/stash/schema.js +127 -0
- package/lib/stash/schema.js.map +1 -0
- package/lib/stash/store.d.ts +92 -0
- package/lib/stash/store.d.ts.map +1 -0
- package/lib/stash/store.js +262 -0
- package/lib/stash/store.js.map +1 -0
- package/lib/stash.d.ts +91 -0
- package/lib/stash.d.ts.map +1 -0
- package/lib/stash.js +296 -0
- package/lib/stash.js.map +1 -0
- package/lib/theme-settings.d.ts +37 -8
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +60 -3
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +1 -1
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +8 -0
- package/lib/theme-tokens.js.map +1 -1
- package/lib/todo-guard.d.ts +116 -0
- package/lib/todo-guard.d.ts.map +1 -0
- package/lib/todo-guard.js +259 -0
- package/lib/todo-guard.js.map +1 -0
- package/lib/ui/editor.d.ts +41 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +98 -4
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/history-picker.d.ts +15 -0
- package/lib/ui/history-picker.d.ts.map +1 -0
- package/lib/ui/history-picker.js +26 -0
- package/lib/ui/history-picker.js.map +1 -0
- package/lib/ui/picker.d.ts +11 -1
- package/lib/ui/picker.d.ts.map +1 -1
- package/lib/ui/picker.js +17 -2
- package/lib/ui/picker.js.map +1 -1
- package/lib/ui/prompt.d.ts +2 -0
- package/lib/ui/prompt.d.ts.map +1 -1
- package/lib/ui/prompt.js +4 -0
- package/lib/ui/prompt.js.map +1 -1
- package/lib/ui/stash-picker.d.ts +43 -0
- package/lib/ui/stash-picker.d.ts.map +1 -0
- package/lib/ui/stash-picker.js +78 -0
- package/lib/ui/stash-picker.js.map +1 -0
- package/lib/ui/status.d.ts +2 -0
- package/lib/ui/status.d.ts.map +1 -1
- package/lib/ui/status.js +7 -0
- package/lib/ui/status.js.map +1 -1
- package/lib/work.d.ts +3 -1
- package/lib/work.d.ts.map +1 -1
- package/lib/work.js +9 -1
- package/lib/work.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Stash entry + file schema and pure parsing/validation helpers.
|
|
2
|
+
//
|
|
3
|
+
// Every disk format is validated on read, so a corrupt or hand-edited stash file
|
|
4
|
+
// can never crash the editor: an unparseable file is treated as empty and
|
|
5
|
+
// quarantined by the store. Keeping this module free of fs dependencies makes
|
|
6
|
+
// the schema fully unit-testable.
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
export const STASH_SCHEMA_VERSION = 1;
|
|
9
|
+
/** A stash longer than this is refused rather than stored. */
|
|
10
|
+
export const MAX_STASH_ENTRY_BYTES = 1_048_576;
|
|
11
|
+
const ENTRY_ID_MAX_LENGTH = 128;
|
|
12
|
+
const ENTRY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
13
|
+
const NUMERIC_SELECTOR_PATTERN = /^\d+$/u;
|
|
14
|
+
const INVALID_ENTRY_ID_MESSAGE = 'invalid stash entry id';
|
|
15
|
+
const ENTRY_TOO_LARGE_MESSAGE = 'stashed draft is too large';
|
|
16
|
+
/**
|
|
17
|
+
* Control characters a draft can never legitimately need: everything below the
|
|
18
|
+
* printable range except the tab and line feed that lay text out, plus DEL and
|
|
19
|
+
* the C1 block.
|
|
20
|
+
*
|
|
21
|
+
* A draft is written straight into a terminal, so a sequence that reached the
|
|
22
|
+
* bank through a hand-edited or hostile file would be executed as a command —
|
|
23
|
+
* clearing the screen, or replacing the reader's clipboard over OSC 52 — instead
|
|
24
|
+
* of appearing as the text they asked for. Stripping at the storage boundary
|
|
25
|
+
* covers every path a draft can take back to the screen.
|
|
26
|
+
*
|
|
27
|
+
* The bidi overrides are stripped with them: they draw nothing, so they cannot
|
|
28
|
+
* be seen and cannot be removed by hand, and what they reorder is what the reader
|
|
29
|
+
* is about to run. A terminal that shapes right-to-left text still has the
|
|
30
|
+
* letters themselves.
|
|
31
|
+
*/
|
|
32
|
+
const DISALLOWED_CONTROL_CHARACTERS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu;
|
|
33
|
+
export function stripControlCharacters(text) {
|
|
34
|
+
return text.replace(DISALLOWED_CONTROL_CHARACTERS, '');
|
|
35
|
+
}
|
|
36
|
+
export function isSafeEntryId(value) {
|
|
37
|
+
return value.length <= ENTRY_ID_MAX_LENGTH && ENTRY_ID_PATTERN.test(value);
|
|
38
|
+
}
|
|
39
|
+
export function assertSafeEntryId(value) {
|
|
40
|
+
if (!isSafeEntryId(value))
|
|
41
|
+
throw new Error(INVALID_ENTRY_ID_MESSAGE);
|
|
42
|
+
}
|
|
43
|
+
/** Refuse one draft that no amount of storage capacity could justify holding. */
|
|
44
|
+
export function assertSafeStashText(text) {
|
|
45
|
+
if (Buffer.byteLength(text, 'utf8') > MAX_STASH_ENTRY_BYTES)
|
|
46
|
+
throw new Error(ENTRY_TOO_LARGE_MESSAGE);
|
|
47
|
+
}
|
|
48
|
+
/** The form of a draft that is safe to hand back to a terminal. */
|
|
49
|
+
export function sanitizeStashText(text) {
|
|
50
|
+
return stripControlCharacters(text);
|
|
51
|
+
}
|
|
52
|
+
export const createNewId = () => randomUUID();
|
|
53
|
+
export function createEmptyStashFile(cwd, now) {
|
|
54
|
+
return { version: STASH_SCHEMA_VERSION, cwd, createdAt: now, updatedAt: now, entries: [] };
|
|
55
|
+
}
|
|
56
|
+
export function isRecord(value) {
|
|
57
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function isValidTimestamp(value) {
|
|
60
|
+
return typeof value === 'number' && Number.isFinite(value) && !Number.isNaN(new Date(value).getTime());
|
|
61
|
+
}
|
|
62
|
+
export function normalizeEntry(raw) {
|
|
63
|
+
if (!isRecord(raw))
|
|
64
|
+
return undefined;
|
|
65
|
+
const { id, text, createdAt } = raw;
|
|
66
|
+
if (typeof id !== 'string' || !isSafeEntryId(id))
|
|
67
|
+
return undefined;
|
|
68
|
+
if (typeof text !== 'string')
|
|
69
|
+
return undefined;
|
|
70
|
+
if (!isValidTimestamp(createdAt))
|
|
71
|
+
return undefined;
|
|
72
|
+
return { id, text: sanitizeStashText(text), createdAt };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read one stash file, or nothing when it is not this surface's current format.
|
|
76
|
+
*
|
|
77
|
+
* Strict on purpose: a duplicate id, a missing timestamp, or a foreign working
|
|
78
|
+
* directory is corruption the store quarantines rather than a shape to repair,
|
|
79
|
+
* because a silently repaired file can resurrect entries the reader thought they
|
|
80
|
+
* dropped — or hand another directory's drafts to this one.
|
|
81
|
+
*/
|
|
82
|
+
export function parseStashFile(raw) {
|
|
83
|
+
if (!isRecord(raw) || raw.version !== STASH_SCHEMA_VERSION)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (typeof raw.cwd !== 'string')
|
|
86
|
+
return undefined;
|
|
87
|
+
if (!isValidTimestamp(raw.createdAt) || !isValidTimestamp(raw.updatedAt))
|
|
88
|
+
return undefined;
|
|
89
|
+
if (!Array.isArray(raw.entries))
|
|
90
|
+
return undefined;
|
|
91
|
+
const entries = [];
|
|
92
|
+
const ids = new Set();
|
|
93
|
+
for (const rawEntry of raw.entries) {
|
|
94
|
+
const entry = normalizeEntry(rawEntry);
|
|
95
|
+
if (entry === undefined || ids.has(entry.id))
|
|
96
|
+
return undefined;
|
|
97
|
+
ids.add(entry.id);
|
|
98
|
+
entries.push(entry);
|
|
99
|
+
}
|
|
100
|
+
return { version: STASH_SCHEMA_VERSION, cwd: raw.cwd, createdAt: raw.createdAt, updatedAt: raw.updatedAt, entries };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a selector against newest-first entries.
|
|
104
|
+
*
|
|
105
|
+
* Index 0 is the newest, mirroring git's index-0-is-tip convention. A selector
|
|
106
|
+
* is an entry id, a numeric index string, or empty for the most recent entry.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveBySelector(entries, selector) {
|
|
109
|
+
if (entries.length === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
const trimmed = selector?.trim();
|
|
112
|
+
if (trimmed === undefined || trimmed === '') {
|
|
113
|
+
const entry = entries[0];
|
|
114
|
+
return entry === undefined ? undefined : { entry, index: 0 };
|
|
115
|
+
}
|
|
116
|
+
const index = entries.findIndex(entry => entry.id === trimmed);
|
|
117
|
+
if (index >= 0)
|
|
118
|
+
return { entry: entries[index], index };
|
|
119
|
+
if (!NUMERIC_SELECTOR_PATTERN.test(trimmed))
|
|
120
|
+
return undefined;
|
|
121
|
+
const numeric = Number(trimmed);
|
|
122
|
+
if (!Number.isSafeInteger(numeric) || numeric >= entries.length)
|
|
123
|
+
return undefined;
|
|
124
|
+
const entry = entries[numeric];
|
|
125
|
+
return entry === undefined ? undefined : { entry, index: numeric };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/stash/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { rename } from 'node:fs/promises';
|
|
2
|
+
import { type StashFailurePhase } from './lock.ts';
|
|
3
|
+
import type { StashPaths } from './paths.ts';
|
|
4
|
+
import { syncDirectoryEntry } from './private-fs.ts';
|
|
5
|
+
import { type Clock, type ResolvedEntry, type StashEntry, type StashFile } from './schema.ts';
|
|
6
|
+
/**
|
|
7
|
+
* A stash file larger than this is refused, never quarantined: hiding a
|
|
8
|
+
* legitimate bank because it grew past a cap would be worse than refusing it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_STASH_FILE_BYTES = 16777216;
|
|
11
|
+
/**
|
|
12
|
+
* A bank that cannot be written because it would no longer be readable.
|
|
13
|
+
*
|
|
14
|
+
* The cap is only useful if it is enforced before the write: a file that grows
|
|
15
|
+
* past it is refused by every later read, so letting one save through would turn
|
|
16
|
+
* a full bank into an unreadable one.
|
|
17
|
+
*/
|
|
18
|
+
export declare class StashFileTooLargeError extends Error {
|
|
19
|
+
readonly bytes: number;
|
|
20
|
+
constructor(bytes: number);
|
|
21
|
+
}
|
|
22
|
+
export interface AddEntryInput {
|
|
23
|
+
readonly text: string;
|
|
24
|
+
/** Caller-supplied id; generated when omitted. */
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
/** Caller-supplied timestamp, used only when a recovery path restores an entry. */
|
|
27
|
+
readonly createdAt?: number;
|
|
28
|
+
}
|
|
29
|
+
type LoadResult = {
|
|
30
|
+
kind: 'ready';
|
|
31
|
+
file: StashFile;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'corrupt';
|
|
34
|
+
quarantinedTo: string | undefined;
|
|
35
|
+
quarantineSyncFailed: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'unsupported';
|
|
38
|
+
version: number;
|
|
39
|
+
};
|
|
40
|
+
/** Where a read moved an unreadable bank, and whether that move is durable. */
|
|
41
|
+
export interface QuarantineRecord {
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly syncFailed: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface StashWriteOutcome {
|
|
46
|
+
readonly committed: true;
|
|
47
|
+
readonly phase: StashFailurePhase;
|
|
48
|
+
readonly error: unknown;
|
|
49
|
+
}
|
|
50
|
+
export type StashWriter = (filePath: string, file: StashFile, syncDirectory?: typeof syncDirectoryEntry, replaceFile?: typeof rename) => Promise<void | StashWriteOutcome>;
|
|
51
|
+
/** Stash data written by a newer build, which this one must not touch. */
|
|
52
|
+
export declare class UnsupportedStashSchemaError extends Error {
|
|
53
|
+
readonly detectedVersion: number;
|
|
54
|
+
readonly supportedVersion: number;
|
|
55
|
+
constructor(detectedVersion: number, supportedVersion?: number);
|
|
56
|
+
}
|
|
57
|
+
export declare class StashStore {
|
|
58
|
+
private file;
|
|
59
|
+
private corruptRecovery;
|
|
60
|
+
private readonly filePath;
|
|
61
|
+
private readonly paths;
|
|
62
|
+
private readonly now;
|
|
63
|
+
private readonly write;
|
|
64
|
+
constructor(paths: StashPaths, loaded: LoadResult, now?: Clock, write?: StashWriter);
|
|
65
|
+
get entries(): readonly StashEntry[];
|
|
66
|
+
get entryCount(): number;
|
|
67
|
+
/** The recovery location the last read produced, if any, reported once. */
|
|
68
|
+
takeQuarantine(): QuarantineRecord | undefined;
|
|
69
|
+
/** Resolve a selector against the entries currently held. */
|
|
70
|
+
find(selector: string | undefined): ResolvedEntry | undefined;
|
|
71
|
+
refresh(): Promise<void>;
|
|
72
|
+
add(input: AddEntryInput): Promise<ResolvedEntry>;
|
|
73
|
+
/** Remove one entry by its exact id, reporting what was removed. */
|
|
74
|
+
removeById(id: string): Promise<ResolvedEntry | undefined>;
|
|
75
|
+
/** Remove the entry a selector names, reporting what was removed. */
|
|
76
|
+
drop(selector: string | undefined): Promise<ResolvedEntry | undefined>;
|
|
77
|
+
/**
|
|
78
|
+
* Remove drafts, reporting how many were dropped.
|
|
79
|
+
*
|
|
80
|
+
* Ids restrict the removal to exactly what a reader confirmed. Clearing
|
|
81
|
+
* whatever is on disk instead would delete drafts another surface added while
|
|
82
|
+
* the confirmation was on screen, which is a loss nobody agreed to.
|
|
83
|
+
*/
|
|
84
|
+
clear(ids?: readonly string[]): Promise<number>;
|
|
85
|
+
private removeAt;
|
|
86
|
+
private persistMutation;
|
|
87
|
+
private reloadFresh;
|
|
88
|
+
}
|
|
89
|
+
export declare function loadStashStore(paths: StashPaths, now?: Clock, write?: StashWriter): Promise<StashStore>;
|
|
90
|
+
export declare function writeStashFile(filePath: string, file: StashFile, syncDirectory?: typeof syncDirectoryEntry, replaceFile?: typeof rename): Promise<void | StashWriteOutcome>;
|
|
91
|
+
export {};
|
|
92
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/stash/store.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAM,MAAM,kBAAkB,CAAA;AAG7C,OAAO,EAIL,KAAK,iBAAiB,EAEvB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAML,kBAAkB,EAEnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,KAAK,KAAK,EAKV,KAAK,aAAa,EAIlB,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,aAAa,CAAA;AAEpB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAa,CAAA;AAO9C;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;gBAEV,KAAK,EAAE,MAAM;CAK1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,mFAAmF;IACnF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,KAAK,UAAU,GACX;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,oBAAoB,EAAE,OAAO,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5C,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,EACf,aAAa,CAAC,EAAE,OAAO,kBAAkB,EACzC,WAAW,CAAC,EAAE,OAAO,MAAM,KACxB,OAAO,CAAC,IAAI,GAAG,iBAAiB,CAAC,CAAA;AAEtC,0EAA0E;AAC1E,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;gBAErB,eAAe,EAAE,MAAM,EAAE,gBAAgB,GAAE,MAA6B;CAQrF;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAW;IACvB,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAO;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;gBAEvB,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAE,KAAgB,EAAE,KAAK,GAAE,WAA4B;IAY7G,IAAI,OAAO,IAAI,SAAS,UAAU,EAAE,CAEnC;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,2EAA2E;IAC3E,cAAc,IAAI,gBAAgB,GAAG,SAAS;IAM9C,6DAA6D;IAC7D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS;IAIvD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAoBvD,oEAAoE;IAC9D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAQhE,qEAAqE;IAC/D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAc5E;;;;;;OAMG;IACG,KAAK,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;YAgBvC,QAAQ;YAYR,eAAe;YASf,WAAW;CAe1B;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,UAAU,EACjB,GAAG,GAAE,KAAgB,EACrB,KAAK,GAAE,WAA4B,GAClC,OAAO,CAAC,UAAU,CAAC,CAIrB;AAwCD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,EACf,aAAa,GAAE,OAAO,kBAAuC,EAC7D,WAAW,GAAE,OAAO,MAAe,GAClC,OAAO,CAAC,IAAI,GAAG,iBAAiB,CAAC,CA4BnC"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// StashStore: on-disk CRUD for stash entries, scoped to one working directory.
|
|
2
|
+
//
|
|
3
|
+
// Entries are stored newest-first (`entries[0]` is `stash@{0}`), mirroring git's
|
|
4
|
+
// index-0-is-tip convention. Every mutation re-reads the file inside an
|
|
5
|
+
// exclusive lock so two surfaces in the same directory cannot lose updates to a
|
|
6
|
+
// read-modify-write race. Writes are atomic (exclusive temp + rename + directory
|
|
7
|
+
// fsync) with tight 0o600/0o700 permissions, because a stashed draft may hold
|
|
8
|
+
// anything the reader was about to send. A corrupt file is quarantined rather
|
|
9
|
+
// than overwritten, so a hand-edit mistake never silently destroys saved drafts.
|
|
10
|
+
import { rename, rm } from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { StashCommittedError, withStashFileLock, withStashMutationLock, } from "./lock.js";
|
|
13
|
+
import { ensurePrivateDirectory, hasErrorCode, quarantinePrivateFile, readPrivateTextFile, syncDirectoryEntry, writePrivateFileExclusive, } from "./private-fs.js";
|
|
14
|
+
import { assertSafeEntryId, assertSafeStashText, createEmptyStashFile, createNewId, isRecord, parseStashFile, resolveBySelector, sanitizeStashText, STASH_SCHEMA_VERSION, } from "./schema.js";
|
|
15
|
+
/**
|
|
16
|
+
* A stash file larger than this is refused, never quarantined: hiding a
|
|
17
|
+
* legitimate bank because it grew past a cap would be worse than refusing it.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_STASH_FILE_BYTES = 16_777_216;
|
|
20
|
+
const DUPLICATE_STASH_ID_MESSAGE = 'duplicate stash id';
|
|
21
|
+
const UNSUPPORTED_SCHEMA_GUIDANCE = 'Upgrade dsh-tui before using this stash, or move the stash file aside for safe recovery.';
|
|
22
|
+
const FILE_TOO_LARGE_MESSAGE = 'this directory has no room left for another draft; drop one first';
|
|
23
|
+
/**
|
|
24
|
+
* A bank that cannot be written because it would no longer be readable.
|
|
25
|
+
*
|
|
26
|
+
* The cap is only useful if it is enforced before the write: a file that grows
|
|
27
|
+
* past it is refused by every later read, so letting one save through would turn
|
|
28
|
+
* a full bank into an unreadable one.
|
|
29
|
+
*/
|
|
30
|
+
export class StashFileTooLargeError extends Error {
|
|
31
|
+
bytes;
|
|
32
|
+
constructor(bytes) {
|
|
33
|
+
super(FILE_TOO_LARGE_MESSAGE);
|
|
34
|
+
this.name = 'StashFileTooLargeError';
|
|
35
|
+
this.bytes = bytes;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Stash data written by a newer build, which this one must not touch. */
|
|
39
|
+
export class UnsupportedStashSchemaError extends Error {
|
|
40
|
+
detectedVersion;
|
|
41
|
+
supportedVersion;
|
|
42
|
+
constructor(detectedVersion, supportedVersion = STASH_SCHEMA_VERSION) {
|
|
43
|
+
super(`stash data uses version ${detectedVersion}; this build supports versions through ${supportedVersion}. ${UNSUPPORTED_SCHEMA_GUIDANCE}`);
|
|
44
|
+
this.name = 'UnsupportedStashSchemaError';
|
|
45
|
+
this.detectedVersion = detectedVersion;
|
|
46
|
+
this.supportedVersion = supportedVersion;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export class StashStore {
|
|
50
|
+
file;
|
|
51
|
+
corruptRecovery;
|
|
52
|
+
filePath;
|
|
53
|
+
paths;
|
|
54
|
+
now;
|
|
55
|
+
write;
|
|
56
|
+
constructor(paths, loaded, now = Date.now, write = writeStashFile) {
|
|
57
|
+
this.paths = paths;
|
|
58
|
+
this.now = now;
|
|
59
|
+
this.write = write;
|
|
60
|
+
this.file = loaded.kind === 'ready' ? loaded.file : createEmptyStashFile(paths.cwd, now());
|
|
61
|
+
this.corruptRecovery =
|
|
62
|
+
loaded.kind === 'corrupt' && loaded.quarantinedTo !== undefined
|
|
63
|
+
? { path: loaded.quarantinedTo, syncFailed: loaded.quarantineSyncFailed }
|
|
64
|
+
: undefined;
|
|
65
|
+
this.filePath = paths.file;
|
|
66
|
+
}
|
|
67
|
+
get entries() {
|
|
68
|
+
return this.file.entries;
|
|
69
|
+
}
|
|
70
|
+
get entryCount() {
|
|
71
|
+
return this.file.entries.length;
|
|
72
|
+
}
|
|
73
|
+
/** The recovery location the last read produced, if any, reported once. */
|
|
74
|
+
takeQuarantine() {
|
|
75
|
+
const recovery = this.corruptRecovery;
|
|
76
|
+
this.corruptRecovery = undefined;
|
|
77
|
+
return recovery;
|
|
78
|
+
}
|
|
79
|
+
/** Resolve a selector against the entries currently held. */
|
|
80
|
+
find(selector) {
|
|
81
|
+
return resolveBySelector(this.file.entries, selector);
|
|
82
|
+
}
|
|
83
|
+
async refresh() {
|
|
84
|
+
await withStashFileLock(this.filePath, () => this.reloadFresh());
|
|
85
|
+
}
|
|
86
|
+
async add(input) {
|
|
87
|
+
const id = input.id ?? createNewId();
|
|
88
|
+
assertSafeEntryId(id);
|
|
89
|
+
assertSafeStashText(input.text);
|
|
90
|
+
// A draft is stored in the form it is safe to draw again, so no path back to
|
|
91
|
+
// the screen has to remember to strip it.
|
|
92
|
+
const text = sanitizeStashText(input.text);
|
|
93
|
+
return withStashMutationLock(this.filePath, async () => {
|
|
94
|
+
await this.reloadFresh();
|
|
95
|
+
if (this.file.entries.some(entry => entry.id === id))
|
|
96
|
+
throw new Error(DUPLICATE_STASH_ID_MESSAGE);
|
|
97
|
+
const entry = { id, text, createdAt: input.createdAt ?? this.now() };
|
|
98
|
+
const next = {
|
|
99
|
+
...this.file,
|
|
100
|
+
updatedAt: entry.createdAt,
|
|
101
|
+
entries: [entry, ...this.file.entries],
|
|
102
|
+
};
|
|
103
|
+
return this.persistMutation(next, { entry, index: 0 });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Remove one entry by its exact id, reporting what was removed. */
|
|
107
|
+
async removeById(id) {
|
|
108
|
+
assertSafeEntryId(id);
|
|
109
|
+
return withStashMutationLock(this.filePath, async () => {
|
|
110
|
+
await this.reloadFresh();
|
|
111
|
+
return this.removeAt(selector => selector === id);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Remove the entry a selector names, reporting what was removed. */
|
|
115
|
+
async drop(selector) {
|
|
116
|
+
return withStashMutationLock(this.filePath, async () => {
|
|
117
|
+
await this.reloadFresh();
|
|
118
|
+
const resolved = resolveBySelector(this.file.entries, selector);
|
|
119
|
+
if (resolved === undefined)
|
|
120
|
+
return { didPersist: false, result: undefined };
|
|
121
|
+
const next = {
|
|
122
|
+
...this.file,
|
|
123
|
+
updatedAt: this.now(),
|
|
124
|
+
entries: this.file.entries.filter((_, index) => index !== resolved.index),
|
|
125
|
+
};
|
|
126
|
+
return this.persistMutation(next, resolved);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove drafts, reporting how many were dropped.
|
|
131
|
+
*
|
|
132
|
+
* Ids restrict the removal to exactly what a reader confirmed. Clearing
|
|
133
|
+
* whatever is on disk instead would delete drafts another surface added while
|
|
134
|
+
* the confirmation was on screen, which is a loss nobody agreed to.
|
|
135
|
+
*/
|
|
136
|
+
async clear(ids) {
|
|
137
|
+
return withStashMutationLock(this.filePath, async () => {
|
|
138
|
+
await this.reloadFresh();
|
|
139
|
+
const confirmed = ids === undefined ? undefined : new Set(ids);
|
|
140
|
+
const kept = confirmed === undefined ? [] : this.file.entries.filter(entry => !confirmed.has(entry.id));
|
|
141
|
+
const removed = this.file.entries.length - kept.length;
|
|
142
|
+
if (removed === 0)
|
|
143
|
+
return { didPersist: false, result: 0 };
|
|
144
|
+
const next = {
|
|
145
|
+
...createEmptyStashFile(this.paths.cwd, this.now()),
|
|
146
|
+
updatedAt: this.now(),
|
|
147
|
+
entries: kept,
|
|
148
|
+
};
|
|
149
|
+
return this.persistMutation(next, removed);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async removeAt(match) {
|
|
153
|
+
const index = this.file.entries.findIndex(entry => match(entry.id));
|
|
154
|
+
const found = index < 0 ? undefined : this.file.entries[index];
|
|
155
|
+
if (found === undefined)
|
|
156
|
+
return { didPersist: false, result: undefined };
|
|
157
|
+
const next = {
|
|
158
|
+
...this.file,
|
|
159
|
+
updatedAt: this.now(),
|
|
160
|
+
entries: this.file.entries.filter((_, position) => position !== index),
|
|
161
|
+
};
|
|
162
|
+
return this.persistMutation(next, { entry: found, index });
|
|
163
|
+
}
|
|
164
|
+
async persistMutation(next, result) {
|
|
165
|
+
const outcome = await this.write(this.filePath, next);
|
|
166
|
+
this.file = next;
|
|
167
|
+
if (outcome?.committed === true) {
|
|
168
|
+
throw new StashCommittedError(result, { phase: outcome.phase, error: outcome.error });
|
|
169
|
+
}
|
|
170
|
+
return { didPersist: true, result };
|
|
171
|
+
}
|
|
172
|
+
async reloadFresh() {
|
|
173
|
+
const loaded = await readCurrentStashFile(this.filePath, this.paths.cwd, this.now());
|
|
174
|
+
if (loaded.kind === 'ready') {
|
|
175
|
+
this.file = loaded.file;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (loaded.kind === 'unsupported')
|
|
179
|
+
throw new UnsupportedStashSchemaError(loaded.version);
|
|
180
|
+
// Corrupt input is quarantined and replaced in memory, so later writes
|
|
181
|
+
// cannot resurrect entries from the invalidated snapshot.
|
|
182
|
+
this.file = createEmptyStashFile(this.paths.cwd, this.now());
|
|
183
|
+
this.corruptRecovery =
|
|
184
|
+
loaded.quarantinedTo === undefined
|
|
185
|
+
? undefined
|
|
186
|
+
: { path: loaded.quarantinedTo, syncFailed: loaded.quarantineSyncFailed };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export async function loadStashStore(paths, now = Date.now, write = writeStashFile) {
|
|
190
|
+
const loaded = await withStashFileLock(paths.file, () => readCurrentStashFile(paths.file, paths.cwd, now()));
|
|
191
|
+
if (loaded.kind === 'unsupported')
|
|
192
|
+
throw new UnsupportedStashSchemaError(loaded.version);
|
|
193
|
+
return new StashStore(paths, loaded, now, write);
|
|
194
|
+
}
|
|
195
|
+
async function readCurrentStashFile(filePath, cwd, now) {
|
|
196
|
+
let source;
|
|
197
|
+
try {
|
|
198
|
+
source = await readPrivateTextFile(filePath, 'stash file', MAX_STASH_FILE_BYTES);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
// A missing bank is an empty one; an oversized or unreadable bank is a real
|
|
202
|
+
// failure the caller must report rather than silently start over from.
|
|
203
|
+
if (hasErrorCode(error, 'ENOENT'))
|
|
204
|
+
return { kind: 'ready', file: createEmptyStashFile(cwd, now) };
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
let raw;
|
|
208
|
+
try {
|
|
209
|
+
raw = JSON.parse(source.text);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return await quarantineCorrupt(filePath, source.identity);
|
|
213
|
+
}
|
|
214
|
+
const parsed = parseStashFile(raw);
|
|
215
|
+
if (parsed !== undefined && parsed.cwd === cwd)
|
|
216
|
+
return { kind: 'ready', file: parsed };
|
|
217
|
+
if (isRecord(raw) &&
|
|
218
|
+
typeof raw.version === 'number' &&
|
|
219
|
+
Number.isSafeInteger(raw.version) &&
|
|
220
|
+
raw.version > STASH_SCHEMA_VERSION) {
|
|
221
|
+
return { kind: 'unsupported', version: raw.version };
|
|
222
|
+
}
|
|
223
|
+
return await quarantineCorrupt(filePath, source.identity);
|
|
224
|
+
}
|
|
225
|
+
async function quarantineCorrupt(filePath, identity) {
|
|
226
|
+
const quarantined = await quarantinePrivateFile(filePath, identity, `corrupt-${Date.now()}`);
|
|
227
|
+
return { kind: 'corrupt', quarantinedTo: quarantined.path, quarantineSyncFailed: quarantined.syncError !== undefined };
|
|
228
|
+
}
|
|
229
|
+
export async function writeStashFile(filePath, file, syncDirectory = syncDirectoryEntry, replaceFile = rename) {
|
|
230
|
+
const directory = path.dirname(filePath);
|
|
231
|
+
await ensurePrivateDirectory(directory);
|
|
232
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
233
|
+
const data = `${JSON.stringify(file, null, 2)}\n`;
|
|
234
|
+
// Every later read refuses a file past this cap, so a write that would cross
|
|
235
|
+
// it has to be refused here instead: the alternative is a bank that saves
|
|
236
|
+
// successfully, clears the editor, and can never be opened again.
|
|
237
|
+
const bytes = Buffer.byteLength(data);
|
|
238
|
+
if (bytes > MAX_STASH_FILE_BYTES)
|
|
239
|
+
throw new StashFileTooLargeError(bytes);
|
|
240
|
+
let tempCreated = false;
|
|
241
|
+
try {
|
|
242
|
+
await writePrivateFileExclusive(tempPath, data);
|
|
243
|
+
tempCreated = true;
|
|
244
|
+
await replaceFile(tempPath, filePath);
|
|
245
|
+
tempCreated = false;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (tempCreated)
|
|
249
|
+
await rm(tempPath, { force: true });
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
// The rename is durable once the directory naming the file is flushed. The
|
|
253
|
+
// directories that name the storage itself were flushed when they were created,
|
|
254
|
+
// so this is the entry the save is actually responsible for.
|
|
255
|
+
try {
|
|
256
|
+
await syncDirectory(directory);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
return { committed: true, phase: 'directory-sync', error };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/stash/store.ts"],"names":[],"mappings":"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"}
|
package/lib/stash.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ResolvedEntry } from './stash/schema.ts';
|
|
2
|
+
import { type StashWriter } from './stash/store.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Everything the stash needs from the surface.
|
|
5
|
+
*
|
|
6
|
+
* The surface owns the editor and the picker, so the stash asks for text and
|
|
7
|
+
* hands back text; it never reaches into a component of its own. `render` is
|
|
8
|
+
* called after every state change so the status count follows the bank, and
|
|
9
|
+
* `editorIsAvailable` is what tells a stash that the bar is the reader's rather
|
|
10
|
+
* than a question's: writing a draft into an answer would submit it as one.
|
|
11
|
+
*/
|
|
12
|
+
export interface StashHost {
|
|
13
|
+
/** The draft as written, with a pasted block expanded back to its text. */
|
|
14
|
+
getEditorText(): string;
|
|
15
|
+
setEditorText(text: string): void;
|
|
16
|
+
/** Whether the prompt bar currently belongs to the reader. */
|
|
17
|
+
editorIsAvailable(): boolean;
|
|
18
|
+
notice(message: string): void;
|
|
19
|
+
/** Ask the reader which entry to take, by id; undefined when they leave. */
|
|
20
|
+
pick(entries: readonly ResolvedEntry[], cwdLabel: string): Promise<string | undefined>;
|
|
21
|
+
/** Ask the reader to confirm clearing `count` drafts. */
|
|
22
|
+
confirm(count: number): Promise<boolean>;
|
|
23
|
+
render(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface PromptStashOptions {
|
|
26
|
+
readonly cwd: string;
|
|
27
|
+
/** Override the storage root; tests keep it inside a scratch directory. */
|
|
28
|
+
readonly baseDir?: string | undefined;
|
|
29
|
+
readonly now?: (() => number) | undefined;
|
|
30
|
+
readonly write?: StashWriter | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare class PromptStash {
|
|
33
|
+
private readonly host;
|
|
34
|
+
private readonly options;
|
|
35
|
+
private readonly paths;
|
|
36
|
+
private readonly now;
|
|
37
|
+
private readonly write;
|
|
38
|
+
private store;
|
|
39
|
+
private count;
|
|
40
|
+
/** The queue a second command waits behind, so writes never interleave. */
|
|
41
|
+
private tail;
|
|
42
|
+
constructor(host: StashHost, options: PromptStashOptions);
|
|
43
|
+
/** How many drafts this directory holds, as last read. */
|
|
44
|
+
get entryCount(): number;
|
|
45
|
+
/** Load the bank once at startup so the status count is real before a command. */
|
|
46
|
+
open(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Persist the draft the reader is looking at, or the one named on the command
|
|
49
|
+
* line.
|
|
50
|
+
*
|
|
51
|
+
* An argument is written into the editor before the write is attempted —
|
|
52
|
+
* before the bank is even opened — because the command line it came from is
|
|
53
|
+
* already gone: a refused write has to leave the draft where the reader can
|
|
54
|
+
* retry it instead of losing the only copy. A command that names nothing — the
|
|
55
|
+
* chord, or a bare `/stash` — parks whatever the bar is holding.
|
|
56
|
+
*/
|
|
57
|
+
stashEditor(typed?: string): Promise<void>;
|
|
58
|
+
/** Put a draft in an empty editor without removing it. */
|
|
59
|
+
apply(selector: string | undefined): Promise<void>;
|
|
60
|
+
/** Put a draft in an empty editor and remove it from the bank. */
|
|
61
|
+
pop(selector: string | undefined): Promise<void>;
|
|
62
|
+
/** Open the list and pop whatever the reader takes. */
|
|
63
|
+
list(cwdLabel: string): Promise<void>;
|
|
64
|
+
/** Delete one draft without using it. */
|
|
65
|
+
drop(selector: string | undefined): Promise<void>;
|
|
66
|
+
/** Confirm, then delete every draft the confirmation named. */
|
|
67
|
+
clear(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Put one entry in an empty editor, then remove it.
|
|
70
|
+
*
|
|
71
|
+
* The editor is filled before the entry is removed, so a crash between the two
|
|
72
|
+
* leaves the draft in the bank rather than only in a terminal that is gone.
|
|
73
|
+
*/
|
|
74
|
+
private popFrom;
|
|
75
|
+
private editorIsReady;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the bar is the reader's to write in.
|
|
78
|
+
*
|
|
79
|
+
* A question borrows the bar and empties it, so an empty check alone would
|
|
80
|
+
* let a pop write the draft into an answer, and the draft's only other copy is
|
|
81
|
+
* deleted right afterwards.
|
|
82
|
+
*/
|
|
83
|
+
private editorIsAvailable;
|
|
84
|
+
private missingMessage;
|
|
85
|
+
private ensure;
|
|
86
|
+
private run;
|
|
87
|
+
private enqueue;
|
|
88
|
+
/** Publish the bank's size and any quarantine the last read produced. */
|
|
89
|
+
private sync;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=stash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stash.d.ts","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAA;AAsBzB;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,aAAa,IAAI,MAAM,CAAA;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,8DAA8D;IAC9D,iBAAiB,IAAI,OAAO,CAAA;IAC5B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,4EAA4E;IAC5E,IAAI,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACtF,yDAAyD;IACzD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,IAAI,IAAI,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CACzC;AASD,qBAAa,WAAW;IAUpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,KAAK,CAAI;IACjB,2EAA2E;IAC3E,OAAO,CAAC,IAAI,CAAmC;gBAG5B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,kBAAkB;IAO9C,0DAA0D;IAC1D,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B;;;;;;;;;OASG;IACH,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC1C,0DAA0D;IAC1D,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAclD,kEAAkE;IAClE,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAShD,uDAAuD;IACvD,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrC,yCAAyC;IACzC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBjD,+DAA+D;IAC/D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB;;;;;OAKG;YACW,OAAO;IAoBrB,OAAO,CAAC,aAAa;IAOrB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,cAAc;YAOR,MAAM;IAOpB,OAAO,CAAC,GAAG;IAYX,OAAO,CAAC,OAAO;IAMf,yEAAyE;IACzE,OAAO,CAAC,IAAI;CAWb"}
|