@sagmans/dsh-tui 0.2.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.
Files changed (174) hide show
  1. package/README.md +282 -25
  2. package/cordis.patch.yml +14 -0
  3. package/lib/agent/model.d.ts +26 -0
  4. package/lib/agent/model.d.ts.map +1 -1
  5. package/lib/agent/model.js +41 -0
  6. package/lib/agent/model.js.map +1 -1
  7. package/lib/agent/projections.d.ts +22 -6
  8. package/lib/agent/projections.d.ts.map +1 -1
  9. package/lib/agent/projections.js +21 -10
  10. package/lib/agent/projections.js.map +1 -1
  11. package/lib/agent/prompt-history.d.ts +89 -0
  12. package/lib/agent/prompt-history.d.ts.map +1 -0
  13. package/lib/agent/prompt-history.js +380 -0
  14. package/lib/agent/prompt-history.js.map +1 -0
  15. package/lib/agent/status.d.ts +16 -0
  16. package/lib/agent/status.d.ts.map +1 -1
  17. package/lib/agent/status.js +3 -0
  18. package/lib/agent/status.js.map +1 -1
  19. package/lib/cards.d.ts +1 -1
  20. package/lib/cards.d.ts.map +1 -1
  21. package/lib/cards.js +4 -3
  22. package/lib/cards.js.map +1 -1
  23. package/lib/gates.d.ts +145 -2
  24. package/lib/gates.d.ts.map +1 -1
  25. package/lib/gates.js +335 -40
  26. package/lib/gates.js.map +1 -1
  27. package/lib/herdr/client.d.ts +57 -0
  28. package/lib/herdr/client.d.ts.map +1 -0
  29. package/lib/herdr/client.js +218 -0
  30. package/lib/herdr/client.js.map +1 -0
  31. package/lib/herdr/constants.d.ts +93 -0
  32. package/lib/herdr/constants.d.ts.map +1 -0
  33. package/lib/herdr/constants.js +79 -0
  34. package/lib/herdr/constants.js.map +1 -0
  35. package/lib/herdr/reporter.d.ts +60 -0
  36. package/lib/herdr/reporter.d.ts.map +1 -0
  37. package/lib/herdr/reporter.js +217 -0
  38. package/lib/herdr/reporter.js.map +1 -0
  39. package/lib/herdr/state.d.ts +57 -0
  40. package/lib/herdr/state.d.ts.map +1 -0
  41. package/lib/herdr/state.js +62 -0
  42. package/lib/herdr/state.js.map +1 -0
  43. package/lib/index.d.ts.map +1 -1
  44. package/lib/index.js +631 -118
  45. package/lib/index.js.map +1 -1
  46. package/lib/input/actions.d.ts +136 -0
  47. package/lib/input/actions.d.ts.map +1 -0
  48. package/lib/input/actions.js +743 -0
  49. package/lib/input/actions.js.map +1 -0
  50. package/lib/input/ghost.d.ts +46 -0
  51. package/lib/input/ghost.d.ts.map +1 -0
  52. package/lib/input/ghost.js +58 -0
  53. package/lib/input/ghost.js.map +1 -0
  54. package/lib/input/keymap-settings.d.ts +21 -0
  55. package/lib/input/keymap-settings.d.ts.map +1 -0
  56. package/lib/input/keymap-settings.js +28 -0
  57. package/lib/input/keymap-settings.js.map +1 -0
  58. package/lib/input/keymap.d.ts +91 -0
  59. package/lib/input/keymap.d.ts.map +1 -0
  60. package/lib/input/keymap.js +172 -0
  61. package/lib/input/keymap.js.map +1 -0
  62. package/lib/input/match.d.ts +10 -0
  63. package/lib/input/match.d.ts.map +1 -0
  64. package/lib/input/match.js +83 -0
  65. package/lib/input/match.js.map +1 -0
  66. package/lib/input/submission.d.ts +29 -1
  67. package/lib/input/submission.d.ts.map +1 -1
  68. package/lib/input/submission.js +33 -2
  69. package/lib/input/submission.js.map +1 -1
  70. package/lib/input.d.ts +17 -0
  71. package/lib/input.d.ts.map +1 -0
  72. package/lib/input.js +27 -0
  73. package/lib/input.js.map +1 -0
  74. package/lib/keys-command.d.ts +16 -0
  75. package/lib/keys-command.d.ts.map +1 -0
  76. package/lib/keys-command.js +60 -0
  77. package/lib/keys-command.js.map +1 -0
  78. package/lib/queue.d.ts +6 -0
  79. package/lib/queue.d.ts.map +1 -0
  80. package/lib/queue.js +68 -0
  81. package/lib/queue.js.map +1 -0
  82. package/lib/settings-notice.d.ts +19 -0
  83. package/lib/settings-notice.d.ts.map +1 -0
  84. package/lib/settings-notice.js +30 -0
  85. package/lib/settings-notice.js.map +1 -0
  86. package/lib/stash/lock.d.ts +72 -0
  87. package/lib/stash/lock.d.ts.map +1 -0
  88. package/lib/stash/lock.js +374 -0
  89. package/lib/stash/lock.js.map +1 -0
  90. package/lib/stash/paths.d.ts +22 -0
  91. package/lib/stash/paths.d.ts.map +1 -0
  92. package/lib/stash/paths.js +83 -0
  93. package/lib/stash/paths.js.map +1 -0
  94. package/lib/stash/private-fs.d.ts +61 -0
  95. package/lib/stash/private-fs.d.ts.map +1 -0
  96. package/lib/stash/private-fs.js +379 -0
  97. package/lib/stash/private-fs.js.map +1 -0
  98. package/lib/stash/schema.d.ts +57 -0
  99. package/lib/stash/schema.d.ts.map +1 -0
  100. package/lib/stash/schema.js +127 -0
  101. package/lib/stash/schema.js.map +1 -0
  102. package/lib/stash/store.d.ts +92 -0
  103. package/lib/stash/store.d.ts.map +1 -0
  104. package/lib/stash/store.js +262 -0
  105. package/lib/stash/store.js.map +1 -0
  106. package/lib/stash.d.ts +91 -0
  107. package/lib/stash.d.ts.map +1 -0
  108. package/lib/stash.js +296 -0
  109. package/lib/stash.js.map +1 -0
  110. package/lib/theme-settings.d.ts +72 -8
  111. package/lib/theme-settings.d.ts.map +1 -1
  112. package/lib/theme-settings.js +125 -5
  113. package/lib/theme-settings.js.map +1 -1
  114. package/lib/theme-tokens.d.ts +4 -4
  115. package/lib/theme-tokens.d.ts.map +1 -1
  116. package/lib/theme-tokens.js +39 -5
  117. package/lib/theme-tokens.js.map +1 -1
  118. package/lib/todo-guard.d.ts +116 -0
  119. package/lib/todo-guard.d.ts.map +1 -0
  120. package/lib/todo-guard.js +259 -0
  121. package/lib/todo-guard.js.map +1 -0
  122. package/lib/ui/editor.d.ts +58 -1
  123. package/lib/ui/editor.d.ts.map +1 -1
  124. package/lib/ui/editor.js +174 -19
  125. package/lib/ui/editor.js.map +1 -1
  126. package/lib/ui/frame.d.ts +54 -0
  127. package/lib/ui/frame.d.ts.map +1 -0
  128. package/lib/ui/frame.js +79 -0
  129. package/lib/ui/frame.js.map +1 -0
  130. package/lib/ui/gate-input.d.ts +20 -0
  131. package/lib/ui/gate-input.d.ts.map +1 -0
  132. package/lib/ui/gate-input.js +96 -0
  133. package/lib/ui/gate-input.js.map +1 -0
  134. package/lib/ui/history-picker.d.ts +15 -0
  135. package/lib/ui/history-picker.d.ts.map +1 -0
  136. package/lib/ui/history-picker.js +26 -0
  137. package/lib/ui/history-picker.js.map +1 -0
  138. package/lib/ui/markdown.d.ts +11 -2
  139. package/lib/ui/markdown.d.ts.map +1 -1
  140. package/lib/ui/markdown.js +25 -6
  141. package/lib/ui/markdown.js.map +1 -1
  142. package/lib/ui/mermaid.d.ts +44 -0
  143. package/lib/ui/mermaid.d.ts.map +1 -0
  144. package/lib/ui/mermaid.js +178 -0
  145. package/lib/ui/mermaid.js.map +1 -0
  146. package/lib/ui/picker.d.ts +40 -8
  147. package/lib/ui/picker.d.ts.map +1 -1
  148. package/lib/ui/picker.js +79 -24
  149. package/lib/ui/picker.js.map +1 -1
  150. package/lib/ui/prompt.d.ts +32 -0
  151. package/lib/ui/prompt.d.ts.map +1 -0
  152. package/lib/ui/prompt.js +49 -0
  153. package/lib/ui/prompt.js.map +1 -0
  154. package/lib/ui/queue.d.ts +24 -0
  155. package/lib/ui/queue.d.ts.map +1 -0
  156. package/lib/ui/queue.js +57 -0
  157. package/lib/ui/queue.js.map +1 -0
  158. package/lib/ui/stash-picker.d.ts +43 -0
  159. package/lib/ui/stash-picker.d.ts.map +1 -0
  160. package/lib/ui/stash-picker.js +78 -0
  161. package/lib/ui/stash-picker.js.map +1 -0
  162. package/lib/ui/status.d.ts +6 -0
  163. package/lib/ui/status.d.ts.map +1 -1
  164. package/lib/ui/status.js +15 -0
  165. package/lib/ui/status.js.map +1 -1
  166. package/lib/ui/view.d.ts +22 -2
  167. package/lib/ui/view.d.ts.map +1 -1
  168. package/lib/ui/view.js +84 -24
  169. package/lib/ui/view.js.map +1 -1
  170. package/lib/work.d.ts +51 -1
  171. package/lib/work.d.ts.map +1 -1
  172. package/lib/work.js +53 -1
  173. package/lib/work.js.map +1 -1
  174. package/package.json +7 -2
@@ -0,0 +1,379 @@
1
+ // No-follow filesystem primitives for sensitive draft storage.
2
+ //
3
+ // Path-based chmod/read helpers can follow a link swapped into place after a
4
+ // check. Opening the validated inode and operating through its handle keeps
5
+ // permission repair and reads bound to the object that was inspected. Ownership
6
+ // and symlink checks are what keep one user's drafts out of another's reach on a
7
+ // shared machine or a network home.
8
+ import { constants } from 'node:fs';
9
+ import { link, lstat, mkdir, open, readlink, rmdir, rm, unlink } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ export const PRIVATE_DIR_MODE = 0o700;
12
+ export const PRIVATE_FILE_MODE = 0o600;
13
+ const MAX_QUARANTINE_ATTEMPTS = 1000;
14
+ const NO_FOLLOW_FLAG = constants.O_NOFOLLOW ?? 0;
15
+ const DIRECTORY_FLAG = constants.O_DIRECTORY ?? 0;
16
+ const ROOT_UID = 0;
17
+ const WORLD_WRITABLE_MODE = 0o002;
18
+ const GROUP_WRITABLE_MODE = 0o020;
19
+ const STICKY_MODE = 0o1000;
20
+ /** The kernel's own limit on a link chain, past which the path cannot be opened anyway. */
21
+ const MAX_LINK_HOPS = 32;
22
+ const TOO_MANY_LINKS_MESSAGE = 'passes through too many links to be checked';
23
+ const UNTRUSTED_ANCESTOR_MESSAGE = 'is writable by other users, so a stash kept there could be redirected to another directory by anyone on this machine';
24
+ /** A file that exceeded the caller's byte budget, which is a refusal, not corruption. */
25
+ export class FileTooLargeError extends Error {
26
+ constructor(label) {
27
+ super(`${label} is too large`);
28
+ this.name = 'FileTooLargeError';
29
+ }
30
+ }
31
+ /**
32
+ * Prepare the storage directory, and make the directories that name it durable.
33
+ *
34
+ * A directory that names a new child holds the only record of that child, so
35
+ * every directory this call creates is flushed through its parent before the
36
+ * call returns. A step that fails takes the created directories away again:
37
+ * leaving them behind would turn the next attempt into one that finds nothing
38
+ * left to flush, and a save could then report success for a tree that a power
39
+ * loss is free to drop.
40
+ *
41
+ * The rollback only ever removes an empty directory. Another surface can reach a
42
+ * directory this call just created and save into it while this one is flushing,
43
+ * and a recursive removal would then delete that surface's committed bank: an
44
+ * entry left unflushed costs durability, while a removed bank costs the draft.
45
+ */
46
+ export async function ensurePrivateDirectory(directory, label = 'storage directory', syncDirectory = syncDirectoryEntry) {
47
+ await assertTrustedAncestors(directory, label);
48
+ const { created, namingParents } = await createPrivateDirectory(directory);
49
+ try {
50
+ const handle = await openValidatedDirectory(directory, label);
51
+ try {
52
+ await handle.chmod(PRIVATE_DIR_MODE);
53
+ await handle.sync();
54
+ }
55
+ finally {
56
+ await handle.close();
57
+ }
58
+ for (const parent of namingParents)
59
+ await syncDirectory(parent);
60
+ }
61
+ catch (error) {
62
+ // Deepest first, so a directory is only removed once nothing is named by it.
63
+ for (const entry of created) {
64
+ try {
65
+ await rmdir(entry);
66
+ }
67
+ catch {
68
+ // Anything still in the way belongs to another writer now, and leaving it
69
+ // is the whole point of not removing the tree recursively.
70
+ break;
71
+ }
72
+ }
73
+ throw error;
74
+ }
75
+ }
76
+ /** Create the directory chain, reporting what it created and what names it. */
77
+ async function createPrivateDirectory(directory) {
78
+ const created = [];
79
+ let current = path.resolve(directory);
80
+ for (;;) {
81
+ if (await pathExists(current))
82
+ break;
83
+ created.push(current);
84
+ const parent = path.dirname(current);
85
+ if (parent === current)
86
+ break;
87
+ current = parent;
88
+ }
89
+ await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
90
+ // The parent of a created directory is where its entry lives; a directory that
91
+ // was itself just created is named by the next step of the walk.
92
+ return { created, namingParents: created.map(entry => path.dirname(entry)) };
93
+ }
94
+ /**
95
+ * Refuse a storage path that anyone but this user (or root) can rewrite.
96
+ *
97
+ * Opening the storage directory without following a link protects that one
98
+ * component, but a directory another user can write is enough to redirect the
99
+ * storage by replacing an entry below it, so every directory that the path
100
+ * passes through has to be checked. The writable bit that matters is any bit but
101
+ * the owner's: a group member can rename an entry just as a stranger can, so only
102
+ * the sticky bit — which reserves renaming to an entry's owner — makes a shared
103
+ * directory acceptable.
104
+ *
105
+ * Links are followed on purpose, because a link like macOS's `/tmp` is not a
106
+ * threat and the permissions of what it resolves to are what count. Each link is
107
+ * therefore read as a link and its target walked in turn, rather than resolved in
108
+ * one step: a chain that jumps through a shared directory would otherwise be
109
+ * checked only where it lands, and the directory it jumped through is the one
110
+ * another user can rewrite.
111
+ */
112
+ async function assertTrustedAncestors(directory, label) {
113
+ const currentUid = process.getuid?.();
114
+ const pending = [path.resolve(directory)];
115
+ const checked = new Set();
116
+ let hops = 0;
117
+ while (pending.length > 0) {
118
+ const current = pending.pop();
119
+ if (checked.has(current))
120
+ continue;
121
+ checked.add(current);
122
+ let stats;
123
+ try {
124
+ stats = await lstat(current);
125
+ }
126
+ catch (error) {
127
+ // A directory that is not there yet is created by the caller; its
128
+ // ancestors still have to be checked, so a miss is not a failure here.
129
+ if (!hasErrorCode(error, 'ENOENT'))
130
+ throw error;
131
+ pending.push(...parentsOf(current));
132
+ continue;
133
+ }
134
+ // A link's own mode decides nothing, but its owner decides everything: in a
135
+ // directory that is sticky, the one account that can repoint a link is the
136
+ // one that owns it, so a link this user does not own is not followed.
137
+ if (stats.isSymbolicLink()) {
138
+ if (currentUid !== undefined && stats.uid !== currentUid && stats.uid !== ROOT_UID) {
139
+ throw new Error(`${label} at ${current} is owned by another user`);
140
+ }
141
+ hops += 1;
142
+ if (hops > MAX_LINK_HOPS)
143
+ throw new Error(`${label} at ${current} ${TOO_MANY_LINKS_MESSAGE}`);
144
+ // The target is not normalized: `..` after a link has to be resolved by the
145
+ // filesystem against what the link points at, and collapsing it here would
146
+ // walk a directory the kernel never visits.
147
+ const target = await readlink(current);
148
+ pending.push(path.isAbsolute(target) ? target : `${path.dirname(current)}/${target}`, ...parentsOf(current));
149
+ continue;
150
+ }
151
+ assertTrustedStats(stats, current, label, currentUid);
152
+ pending.push(...parentsOf(current));
153
+ }
154
+ }
155
+ /** The one directory that names `current`, if it has one. */
156
+ function parentsOf(current) {
157
+ const parent = path.dirname(current);
158
+ return parent === current ? [] : [parent];
159
+ }
160
+ function assertTrustedStats(stats, directory, label, currentUid) {
161
+ if (currentUid !== undefined && stats.uid !== currentUid && stats.uid !== ROOT_UID) {
162
+ throw new Error(`${label} at ${directory} is owned by another user`);
163
+ }
164
+ const shared = stats.mode & (WORLD_WRITABLE_MODE | GROUP_WRITABLE_MODE);
165
+ if (shared !== 0 && (stats.mode & STICKY_MODE) === 0) {
166
+ throw new Error(`${label} at ${directory} ${UNTRUSTED_ANCESTOR_MESSAGE}`);
167
+ }
168
+ }
169
+ /**
170
+ * Flush one directory entry without asserting who owns the directory.
171
+ *
172
+ * An ancestor may legitimately be root-owned (`/tmp`, a mount point) and still
173
+ * have to be synced for the entry below it to survive a power loss, so the
174
+ * ownership rule that guards the storage directory itself is not applied here.
175
+ * A directory that is already gone needs no flushing.
176
+ */
177
+ export async function syncDirectoryEntry(directory) {
178
+ let handle;
179
+ try {
180
+ handle = await open(directory, constants.O_RDONLY | DIRECTORY_FLAG);
181
+ }
182
+ catch (error) {
183
+ if (hasErrorCode(error, 'ENOENT'))
184
+ return;
185
+ throw error;
186
+ }
187
+ try {
188
+ await handle.sync();
189
+ }
190
+ finally {
191
+ await handle.close();
192
+ }
193
+ }
194
+ export async function assertPrivateDirectory(directory, label) {
195
+ const handle = await openValidatedDirectory(directory, label);
196
+ try {
197
+ return await handle.stat();
198
+ }
199
+ finally {
200
+ await handle.close();
201
+ }
202
+ }
203
+ export async function syncPrivateDirectory(directory, label = 'storage directory') {
204
+ const handle = await openValidatedDirectory(directory, label);
205
+ try {
206
+ await handle.sync();
207
+ }
208
+ finally {
209
+ await handle.close();
210
+ }
211
+ }
212
+ export async function readPrivateTextFile(filePath, label = 'stash file', maxBytes) {
213
+ const before = await lstat(filePath);
214
+ assertRegularOwnedFile(before, label);
215
+ const handle = await open(filePath, constants.O_RDONLY | NO_FOLLOW_FLAG);
216
+ try {
217
+ const opened = await handle.stat();
218
+ assertRegularOwnedFile(opened, label);
219
+ assertSameIdentity(before, opened, label);
220
+ await handle.chmod(PRIVATE_FILE_MODE);
221
+ return { text: await readText(handle, maxBytes, label), identity: identityOf(opened) };
222
+ }
223
+ finally {
224
+ await handle.close();
225
+ }
226
+ }
227
+ async function readText(handle, maxBytes, label) {
228
+ if (maxBytes === undefined)
229
+ return handle.readFile('utf8');
230
+ const buffer = Buffer.alloc(maxBytes + 1);
231
+ let bytesRead = 0;
232
+ while (bytesRead < buffer.length) {
233
+ const chunk = await handle.read(buffer, bytesRead, buffer.length - bytesRead, null);
234
+ if (chunk.bytesRead === 0)
235
+ break;
236
+ bytesRead += chunk.bytesRead;
237
+ }
238
+ if (bytesRead > maxBytes)
239
+ throw new FileTooLargeError(label);
240
+ return buffer.subarray(0, bytesRead).toString('utf8');
241
+ }
242
+ export async function writePrivateFileExclusive(filePath, data) {
243
+ const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NO_FOLLOW_FLAG;
244
+ const handle = await open(filePath, flags, PRIVATE_FILE_MODE);
245
+ let succeeded = false;
246
+ try {
247
+ await handle.writeFile(data);
248
+ await handle.chmod(PRIVATE_FILE_MODE);
249
+ await handle.sync();
250
+ succeeded = true;
251
+ }
252
+ finally {
253
+ await handle.close();
254
+ if (!succeeded)
255
+ await rm(filePath, { force: true });
256
+ }
257
+ }
258
+ /**
259
+ * Move a file aside without losing it.
260
+ *
261
+ * A hard-link reservation keeps the original bytes reachable under a new name
262
+ * before the source is unlinked, so an interrupted quarantine leaves either the
263
+ * readable original or the readable copy, never neither.
264
+ */
265
+ export async function quarantinePrivateFile(filePath, identity, label, syncDirectory = syncPrivateDirectory) {
266
+ for (let attempt = 0; attempt < MAX_QUARANTINE_ATTEMPTS; attempt += 1) {
267
+ const candidate = `${filePath}.${label}${attempt === 0 ? '' : `-${attempt}`}`;
268
+ try {
269
+ await link(filePath, candidate);
270
+ }
271
+ catch (error) {
272
+ if (hasErrorCode(error, 'EEXIST'))
273
+ continue;
274
+ throw error;
275
+ }
276
+ let moved = false;
277
+ try {
278
+ const candidateStats = await lstat(candidate);
279
+ const sourceStats = await lstat(filePath);
280
+ assertSameIdentity(candidateStats, identity, 'quarantine source');
281
+ assertSameIdentity(sourceStats, identity, 'quarantine source');
282
+ await unlink(filePath);
283
+ moved = true;
284
+ try {
285
+ await syncDirectory(path.dirname(filePath));
286
+ }
287
+ catch (error) {
288
+ // The bytes are already under the recovery name and the original is
289
+ // gone, so the path is the one thing the reader must still be told.
290
+ // Throwing here would lose it and leave the next read starting empty
291
+ // with no notice at all.
292
+ return { path: candidate, syncError: error };
293
+ }
294
+ return { path: candidate, syncError: undefined };
295
+ }
296
+ finally {
297
+ if (!moved)
298
+ await unlink(candidate).catch(() => undefined);
299
+ }
300
+ }
301
+ throw new Error(`unable to reserve quarantine path for ${filePath}`);
302
+ }
303
+ export async function removePrivateDirectory(directory, label = 'storage directory') {
304
+ let stats;
305
+ try {
306
+ stats = await lstat(directory);
307
+ }
308
+ catch (error) {
309
+ if (hasErrorCode(error, 'ENOENT'))
310
+ return;
311
+ throw error;
312
+ }
313
+ assertDirectory(stats, label);
314
+ assertCurrentUserOwns(stats, label);
315
+ await rm(directory, { recursive: true });
316
+ await syncPrivateDirectory(path.dirname(directory));
317
+ }
318
+ async function openValidatedDirectory(directory, label) {
319
+ const before = await lstat(directory);
320
+ assertDirectory(before, label);
321
+ assertCurrentUserOwns(before, label);
322
+ const handle = await open(directory, constants.O_RDONLY | DIRECTORY_FLAG | NO_FOLLOW_FLAG);
323
+ try {
324
+ const opened = await handle.stat();
325
+ assertDirectory(opened, label);
326
+ assertCurrentUserOwns(opened, label);
327
+ assertSameIdentity(before, opened, label);
328
+ return handle;
329
+ }
330
+ catch (error) {
331
+ await handle.close();
332
+ throw error;
333
+ }
334
+ }
335
+ function assertDirectory(stats, label) {
336
+ if (stats.isSymbolicLink())
337
+ throw new Error(`${label} must not be a symbolic link`);
338
+ if (!stats.isDirectory())
339
+ throw new Error(`${label} must be a directory`);
340
+ }
341
+ export function assertRegularOwnedFile(stats, label) {
342
+ if (stats.isSymbolicLink())
343
+ throw new Error(`${label} must not be a symbolic link`);
344
+ if (!stats.isFile())
345
+ throw new Error(`${label} must be a regular file`);
346
+ assertCurrentUserOwns(stats, label);
347
+ }
348
+ function assertCurrentUserOwns(stats, label) {
349
+ const currentUid = process.getuid?.();
350
+ if (currentUid !== undefined && stats.uid !== currentUid) {
351
+ throw new Error(`${label} must be owned by the current user`);
352
+ }
353
+ }
354
+ function assertSameIdentity(actual, expected, label) {
355
+ if (actual.dev !== expected.dev || actual.ino !== expected.ino) {
356
+ throw new Error(`${label} changed during validation`);
357
+ }
358
+ }
359
+ function identityOf(stats) {
360
+ return { dev: stats.dev, ino: stats.ino };
361
+ }
362
+ export async function pathExists(target) {
363
+ try {
364
+ await lstat(target);
365
+ return true;
366
+ }
367
+ catch (error) {
368
+ if (hasErrorCode(error, 'ENOENT'))
369
+ return false;
370
+ throw error;
371
+ }
372
+ }
373
+ export function hasErrorCode(value, code) {
374
+ return (typeof value === 'object' &&
375
+ value !== null &&
376
+ 'code' in value &&
377
+ value.code === code);
378
+ }
379
+ //# sourceMappingURL=private-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"private-fs.js","sourceRoot":"","sources":["../../src/stash/private-fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ export declare const STASH_SCHEMA_VERSION = 1;
2
+ /** A stash longer than this is refused rather than stored. */
3
+ export declare const MAX_STASH_ENTRY_BYTES = 1048576;
4
+ export declare function stripControlCharacters(text: string): string;
5
+ export declare function isSafeEntryId(value: string): boolean;
6
+ export declare function assertSafeEntryId(value: string): void;
7
+ /** Refuse one draft that no amount of storage capacity could justify holding. */
8
+ export declare function assertSafeStashText(text: string): void;
9
+ /** The form of a draft that is safe to hand back to a terminal. */
10
+ export declare function sanitizeStashText(text: string): string;
11
+ export interface StashEntry {
12
+ readonly id: string;
13
+ readonly text: string;
14
+ readonly createdAt: number;
15
+ }
16
+ export interface StashFile {
17
+ readonly version: typeof STASH_SCHEMA_VERSION;
18
+ /**
19
+ * The exact working directory this bank belongs to.
20
+ *
21
+ * The key alone cannot prove ownership: it is a flattened label that two
22
+ * directories could once have shared. The exact path is compared on every
23
+ * read, so a file that belongs to another directory is quarantined rather than
24
+ * read or deleted as this one's.
25
+ */
26
+ readonly cwd: string;
27
+ readonly createdAt: number;
28
+ readonly updatedAt: number;
29
+ /** Newest first: `entries[0]` is `stash@{0}`. */
30
+ readonly entries: readonly StashEntry[];
31
+ }
32
+ export type Clock = () => number;
33
+ export declare const createNewId: () => string;
34
+ export declare function createEmptyStashFile(cwd: string, now: number): StashFile;
35
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
36
+ export declare function normalizeEntry(raw: unknown): StashEntry | undefined;
37
+ /**
38
+ * Read one stash file, or nothing when it is not this surface's current format.
39
+ *
40
+ * Strict on purpose: a duplicate id, a missing timestamp, or a foreign working
41
+ * directory is corruption the store quarantines rather than a shape to repair,
42
+ * because a silently repaired file can resurrect entries the reader thought they
43
+ * dropped — or hand another directory's drafts to this one.
44
+ */
45
+ export declare function parseStashFile(raw: unknown): StashFile | undefined;
46
+ export interface ResolvedEntry {
47
+ readonly entry: StashEntry;
48
+ readonly index: number;
49
+ }
50
+ /**
51
+ * Resolve a selector against newest-first entries.
52
+ *
53
+ * Index 0 is the newest, mirroring git's index-0-is-tip convention. A selector
54
+ * is an entry id, a numeric index string, or empty for the most recent entry.
55
+ */
56
+ export declare function resolveBySelector(entries: readonly StashEntry[], selector: string | undefined): ResolvedEntry | undefined;
57
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/stash/schema.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,8DAA8D;AAC9D,eAAO,MAAM,qBAAqB,UAAY,CAAA;AA2B9C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAErD;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,oBAAoB,CAAA;IAC7C;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAA;CACxC;AAED,MAAM,MAAM,KAAK,GAAG,MAAM,MAAM,CAAA;AAEhC,eAAO,MAAM,WAAW,QAAO,MAAsB,CAAA;AAErD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAExE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAMD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAOnE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAclE;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,aAAa,GAAG,SAAS,CAc3B"}
@@ -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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"}