@elaraai/e3-ui-cli 1.0.72 → 1.0.74

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 (220) hide show
  1. package/README.md +46 -21
  2. package/bin/e3-ui.mjs +6 -0
  3. package/dist/app/assets/{engine-BKZbmXkC-DmcCFcnb.js → engine-Bm-cIDd_-DwUdGcTZ.js} +1 -1
  4. package/dist/app/assets/{index-BJNWHnRv.js → index-mj-YVVN1.js} +91 -91
  5. package/dist/app/index.html +1 -1
  6. package/dist/cli.js +36 -5
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/tui.d.ts +84 -0
  9. package/dist/commands/tui.d.ts.map +1 -0
  10. package/dist/commands/tui.js +89 -0
  11. package/dist/commands/tui.js.map +1 -0
  12. package/dist/tui/api.d.ts +108 -0
  13. package/dist/tui/api.d.ts.map +1 -0
  14. package/dist/tui/api.js +130 -0
  15. package/dist/tui/api.js.map +1 -0
  16. package/dist/tui/app.d.ts +23 -0
  17. package/dist/tui/app.d.ts.map +1 -0
  18. package/dist/tui/app.js +244 -0
  19. package/dist/tui/app.js.map +1 -0
  20. package/dist/tui/controller.d.ts +116 -0
  21. package/dist/tui/controller.d.ts.map +1 -0
  22. package/dist/tui/controller.js +644 -0
  23. package/dist/tui/controller.js.map +1 -0
  24. package/dist/tui/data/dataflow.d.ts +68 -0
  25. package/dist/tui/data/dataflow.d.ts.map +1 -0
  26. package/dist/tui/data/dataflow.js +137 -0
  27. package/dist/tui/data/dataflow.js.map +1 -0
  28. package/dist/tui/data/dataset.d.ts +80 -0
  29. package/dist/tui/data/dataset.d.ts.map +1 -0
  30. package/dist/tui/data/dataset.js +297 -0
  31. package/dist/tui/data/dataset.js.map +1 -0
  32. package/dist/tui/data/edit-buffer.d.ts +74 -0
  33. package/dist/tui/data/edit-buffer.d.ts.map +1 -0
  34. package/dist/tui/data/edit-buffer.js +120 -0
  35. package/dist/tui/data/edit-buffer.js.map +1 -0
  36. package/dist/tui/data/feeds.d.ts +45 -0
  37. package/dist/tui/data/feeds.d.ts.map +1 -0
  38. package/dist/tui/data/feeds.js +318 -0
  39. package/dist/tui/data/feeds.js.map +1 -0
  40. package/dist/tui/data/logs.d.ts +68 -0
  41. package/dist/tui/data/logs.d.ts.map +1 -0
  42. package/dist/tui/data/logs.js +134 -0
  43. package/dist/tui/data/logs.js.map +1 -0
  44. package/dist/tui/input/commands.d.ts +159 -0
  45. package/dist/tui/input/commands.d.ts.map +1 -0
  46. package/dist/tui/input/commands.js +259 -0
  47. package/dist/tui/input/commands.js.map +1 -0
  48. package/dist/tui/input/completion.d.ts +63 -0
  49. package/dist/tui/input/completion.d.ts.map +1 -0
  50. package/dist/tui/input/completion.js +128 -0
  51. package/dist/tui/input/completion.js.map +1 -0
  52. package/dist/tui/input/keymap.d.ts +153 -0
  53. package/dist/tui/input/keymap.d.ts.map +1 -0
  54. package/dist/tui/input/keymap.js +206 -0
  55. package/dist/tui/input/keymap.js.map +1 -0
  56. package/dist/tui/input/mouse.d.ts +52 -0
  57. package/dist/tui/input/mouse.d.ts.map +1 -0
  58. package/dist/tui/input/mouse.js +55 -0
  59. package/dist/tui/input/mouse.js.map +1 -0
  60. package/dist/tui/lifecycle.d.ts +61 -0
  61. package/dist/tui/lifecycle.d.ts.map +1 -0
  62. package/dist/tui/lifecycle.js +99 -0
  63. package/dist/tui/lifecycle.js.map +1 -0
  64. package/dist/tui/model/catalogue.d.ts +28 -0
  65. package/dist/tui/model/catalogue.d.ts.map +1 -0
  66. package/dist/tui/model/catalogue.js +94 -0
  67. package/dist/tui/model/catalogue.js.map +1 -0
  68. package/dist/tui/model/help.d.ts +37 -0
  69. package/dist/tui/model/help.d.ts.map +1 -0
  70. package/dist/tui/model/help.js +210 -0
  71. package/dist/tui/model/help.js.map +1 -0
  72. package/dist/tui/model/index.d.ts +42 -0
  73. package/dist/tui/model/index.d.ts.map +1 -0
  74. package/dist/tui/model/index.js +50 -0
  75. package/dist/tui/model/index.js.map +1 -0
  76. package/dist/tui/model/status.d.ts +88 -0
  77. package/dist/tui/model/status.d.ts.map +1 -0
  78. package/dist/tui/model/status.js +137 -0
  79. package/dist/tui/model/status.js.map +1 -0
  80. package/dist/tui/model/tree.d.ts +126 -0
  81. package/dist/tui/model/tree.d.ts.map +1 -0
  82. package/dist/tui/model/tree.js +217 -0
  83. package/dist/tui/model/tree.js.map +1 -0
  84. package/dist/tui/model/types.d.ts +21 -0
  85. package/dist/tui/model/types.d.ts.map +1 -0
  86. package/dist/tui/model/types.js +46 -0
  87. package/dist/tui/model/types.js.map +1 -0
  88. package/dist/tui/render/glyphs.d.ts +122 -0
  89. package/dist/tui/render/glyphs.d.ts.map +1 -0
  90. package/dist/tui/render/glyphs.js +122 -0
  91. package/dist/tui/render/glyphs.js.map +1 -0
  92. package/dist/tui/render/layout.d.ts +145 -0
  93. package/dist/tui/render/layout.d.ts.map +1 -0
  94. package/dist/tui/render/layout.js +209 -0
  95. package/dist/tui/render/layout.js.map +1 -0
  96. package/dist/tui/render/text.d.ts +153 -0
  97. package/dist/tui/render/text.d.ts.map +1 -0
  98. package/dist/tui/render/text.js +320 -0
  99. package/dist/tui/render/text.js.map +1 -0
  100. package/dist/tui/render/theme.d.ts +88 -0
  101. package/dist/tui/render/theme.d.ts.map +1 -0
  102. package/dist/tui/render/theme.js +145 -0
  103. package/dist/tui/render/theme.js.map +1 -0
  104. package/dist/tui/session.d.ts +78 -0
  105. package/dist/tui/session.d.ts.map +1 -0
  106. package/dist/tui/session.js +237 -0
  107. package/dist/tui/session.js.map +1 -0
  108. package/dist/tui/state/actions.d.ts +584 -0
  109. package/dist/tui/state/actions.d.ts.map +1 -0
  110. package/dist/tui/state/actions.js +62 -0
  111. package/dist/tui/state/actions.js.map +1 -0
  112. package/dist/tui/state/persist.d.ts +114 -0
  113. package/dist/tui/state/persist.d.ts.map +1 -0
  114. package/dist/tui/state/persist.js +187 -0
  115. package/dist/tui/state/persist.js.map +1 -0
  116. package/dist/tui/state/poll.d.ts +99 -0
  117. package/dist/tui/state/poll.d.ts.map +1 -0
  118. package/dist/tui/state/poll.js +160 -0
  119. package/dist/tui/state/poll.js.map +1 -0
  120. package/dist/tui/state/reducer.d.ts +16 -0
  121. package/dist/tui/state/reducer.d.ts.map +1 -0
  122. package/dist/tui/state/reducer.js +363 -0
  123. package/dist/tui/state/reducer.js.map +1 -0
  124. package/dist/tui/state/store.d.ts +37 -0
  125. package/dist/tui/state/store.d.ts.map +1 -0
  126. package/dist/tui/state/store.js +84 -0
  127. package/dist/tui/state/store.js.map +1 -0
  128. package/dist/tui/suspend.d.ts +17 -0
  129. package/dist/tui/suspend.d.ts.map +1 -0
  130. package/dist/tui/suspend.js +33 -0
  131. package/dist/tui/suspend.js.map +1 -0
  132. package/dist/tui/ui/App.d.ts +44 -0
  133. package/dist/tui/ui/App.d.ts.map +1 -0
  134. package/dist/tui/ui/App.js +105 -0
  135. package/dist/tui/ui/App.js.map +1 -0
  136. package/dist/tui/ui/frame.d.ts +86 -0
  137. package/dist/tui/ui/frame.d.ts.map +1 -0
  138. package/dist/tui/ui/frame.js +42 -0
  139. package/dist/tui/ui/frame.js.map +1 -0
  140. package/dist/tui/ui/lines.d.ts +92 -0
  141. package/dist/tui/ui/lines.d.ts.map +1 -0
  142. package/dist/tui/ui/lines.js +94 -0
  143. package/dist/tui/ui/lines.js.map +1 -0
  144. package/dist/tui/ui/shell/chrome.d.ts +61 -0
  145. package/dist/tui/ui/shell/chrome.d.ts.map +1 -0
  146. package/dist/tui/ui/shell/chrome.js +272 -0
  147. package/dist/tui/ui/shell/chrome.js.map +1 -0
  148. package/dist/tui/ui/shell/widgets.d.ts +116 -0
  149. package/dist/tui/ui/shell/widgets.d.ts.map +1 -0
  150. package/dist/tui/ui/shell/widgets.js +183 -0
  151. package/dist/tui/ui/shell/widgets.js.map +1 -0
  152. package/dist/tui/ui/views/about.d.ts +20 -0
  153. package/dist/tui/ui/views/about.d.ts.map +1 -0
  154. package/dist/tui/ui/views/about.js +40 -0
  155. package/dist/tui/ui/views/about.js.map +1 -0
  156. package/dist/tui/ui/views/all.d.ts +17 -0
  157. package/dist/tui/ui/views/all.d.ts.map +1 -0
  158. package/dist/tui/ui/views/all.js +17 -0
  159. package/dist/tui/ui/views/all.js.map +1 -0
  160. package/dist/tui/ui/views/dashboard.d.ts +211 -0
  161. package/dist/tui/ui/views/dashboard.d.ts.map +1 -0
  162. package/dist/tui/ui/views/dashboard.js +651 -0
  163. package/dist/tui/ui/views/dashboard.js.map +1 -0
  164. package/dist/tui/ui/views/help.d.ts +17 -0
  165. package/dist/tui/ui/views/help.d.ts.map +1 -0
  166. package/dist/tui/ui/views/help.js +65 -0
  167. package/dist/tui/ui/views/help.js.map +1 -0
  168. package/dist/tui/ui/views/index.d.ts +42 -0
  169. package/dist/tui/ui/views/index.d.ts.map +1 -0
  170. package/dist/tui/ui/views/index.js +42 -0
  171. package/dist/tui/ui/views/index.js.map +1 -0
  172. package/dist/tui/ui/views/input.d.ts +6 -0
  173. package/dist/tui/ui/views/input.d.ts.map +1 -0
  174. package/dist/tui/ui/views/input.js +494 -0
  175. package/dist/tui/ui/views/input.js.map +1 -0
  176. package/dist/tui/ui/views/launch.d.ts +16 -0
  177. package/dist/tui/ui/views/launch.d.ts.map +1 -0
  178. package/dist/tui/ui/views/launch.js +43 -0
  179. package/dist/tui/ui/views/launch.js.map +1 -0
  180. package/dist/tui/ui/views/refusal.d.ts +20 -0
  181. package/dist/tui/ui/views/refusal.d.ts.map +1 -0
  182. package/dist/tui/ui/views/refusal.js +47 -0
  183. package/dist/tui/ui/views/refusal.js.map +1 -0
  184. package/dist/tui/ui/views/repos.d.ts +10 -0
  185. package/dist/tui/ui/views/repos.d.ts.map +1 -0
  186. package/dist/tui/ui/views/repos.js +93 -0
  187. package/dist/tui/ui/views/repos.js.map +1 -0
  188. package/dist/tui/ui/views/task.d.ts +17 -0
  189. package/dist/tui/ui/views/task.d.ts.map +1 -0
  190. package/dist/tui/ui/views/task.js +309 -0
  191. package/dist/tui/ui/views/task.js.map +1 -0
  192. package/dist/tui/ui/views/wordmark.d.ts +15 -0
  193. package/dist/tui/ui/views/wordmark.d.ts.map +1 -0
  194. package/dist/tui/ui/views/wordmark.js +50 -0
  195. package/dist/tui/ui/views/wordmark.js.map +1 -0
  196. package/dist/tui/ui/views/workspaces.d.ts +17 -0
  197. package/dist/tui/ui/views/workspaces.d.ts.map +1 -0
  198. package/dist/tui/ui/views/workspaces.js +127 -0
  199. package/dist/tui/ui/views/workspaces.js.map +1 -0
  200. package/dist/tui/ui/widgets/leaf-editor.d.ts +58 -0
  201. package/dist/tui/ui/widgets/leaf-editor.d.ts.map +1 -0
  202. package/dist/tui/ui/widgets/leaf-editor.js +119 -0
  203. package/dist/tui/ui/widgets/leaf-editor.js.map +1 -0
  204. package/dist/tui/ui/widgets/logs.d.ts +99 -0
  205. package/dist/tui/ui/widgets/logs.d.ts.map +1 -0
  206. package/dist/tui/ui/widgets/logs.js +291 -0
  207. package/dist/tui/ui/widgets/logs.js.map +1 -0
  208. package/dist/tui/ui/widgets/reads.d.ts +62 -0
  209. package/dist/tui/ui/widgets/reads.d.ts.map +1 -0
  210. package/dist/tui/ui/widgets/reads.js +122 -0
  211. package/dist/tui/ui/widgets/reads.js.map +1 -0
  212. package/dist/tui/ui/widgets/runs.d.ts +51 -0
  213. package/dist/tui/ui/widgets/runs.d.ts.map +1 -0
  214. package/dist/tui/ui/widgets/runs.js +86 -0
  215. package/dist/tui/ui/widgets/runs.js.map +1 -0
  216. package/dist/tui/ui/widgets/tree.d.ts +149 -0
  217. package/dist/tui/ui/widgets/tree.d.ts.map +1 -0
  218. package/dist/tui/ui/widgets/tree.js +584 -0
  219. package/dist/tui/ui/widgets/tree.js.map +1 -0
  220. package/package.json +22 -12
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /** The state schema version this build reads and writes. */
6
+ export declare const STATE_VERSION = 1;
7
+ /** Tree entries kept (least recently used dropped first). */
8
+ export declare const MAX_TREE_ENTRIES = 200;
9
+ /** Debounce between a change and the write, in milliseconds. */
10
+ export declare const WRITE_DEBOUNCE_MS = 250;
11
+ /** A value tree's remembered UI state. */
12
+ export interface TreeState {
13
+ /** Per-row expansion overrides keyed by row id. */
14
+ open: Record<string, boolean>;
15
+ /** The first visible flat row. */
16
+ topRow: number;
17
+ /** The collapse-all / expand-all override of the open depth. */
18
+ baseDepth?: number | undefined;
19
+ /** Epoch milliseconds of the last touch (the LRU key). */
20
+ touched: number;
21
+ }
22
+ /** A repository's remembered state. */
23
+ export interface RepoState {
24
+ /** The last workspace opened. */
25
+ workspace?: string | undefined;
26
+ /** The last view (`dashboard`, `task:<name>`, `input:<name>`). */
27
+ view?: string | undefined;
28
+ /** Value trees by storage key (`${ws}:${path}`). */
29
+ trees: Record<string, TreeState>;
30
+ }
31
+ /** The whole state file. */
32
+ export interface PersistedState {
33
+ version: typeof STATE_VERSION;
34
+ /** The last repository argument opened. */
35
+ lastRepo?: string | undefined;
36
+ /** Repositories by key (the resolved local path or the remote URL). */
37
+ repos: Record<string, RepoState>;
38
+ }
39
+ /** An empty state file. */
40
+ export declare function emptyState(): PersistedState;
41
+ /**
42
+ * Resolves the state file path.
43
+ *
44
+ * @param env - The environment
45
+ * @param platform - `process.platform`
46
+ * @param home - The home directory
47
+ * @returns `$E3_UI_STATE`, else `$XDG_STATE_HOME/e3-ui/state.json`, else the platform default
48
+ */
49
+ export declare function statePath(env: NodeJS.ProcessEnv, platform: NodeJS.Platform, home: string): string;
50
+ /**
51
+ * Whether a parsed value is a state file this build can read.
52
+ *
53
+ * @param value - The parsed JSON
54
+ * @returns `true` for a v1 object with a `repos` record
55
+ */
56
+ export declare function isPersistedState(value: unknown): value is PersistedState;
57
+ /**
58
+ * Loads the state file. A missing file starts fresh; a corrupt or
59
+ * incompatible one is renamed to `<file>.bak` and started fresh.
60
+ *
61
+ * @param file - The state file path
62
+ * @returns The state and, when the file was set aside, the backup path
63
+ */
64
+ export declare function loadState(file: string): {
65
+ state: PersistedState;
66
+ recoveredFrom?: string;
67
+ };
68
+ /**
69
+ * Writes the state file atomically (temp file + rename) with mode 0600,
70
+ * creating its directory (0700) as needed.
71
+ *
72
+ * @param file - The state file path
73
+ * @param state - The state
74
+ */
75
+ export declare function writeStateSync(file: string, state: PersistedState): void;
76
+ /**
77
+ * Drops the least recently touched tree entries beyond the cap.
78
+ *
79
+ * @param state - The state (mutated)
80
+ * @param max - The cap
81
+ */
82
+ export declare function pruneTrees(state: PersistedState, max?: number): void;
83
+ /** The debounced writer. */
84
+ export interface Persister {
85
+ /** The current state (read-only view). */
86
+ readonly state: PersistedState;
87
+ /** Applies a change and schedules a write. */
88
+ update(fn: (state: PersistedState) => void): void;
89
+ /** Writes now if anything is pending. */
90
+ flush(): void;
91
+ /** Whether a write is pending. */
92
+ readonly dirty: boolean;
93
+ }
94
+ /**
95
+ * Creates the debounced writer over a state file.
96
+ *
97
+ * @param file - The state file path
98
+ * @param initial - The loaded state
99
+ * @param options - `debounceMs` (default 250) and `onError` for write failures
100
+ * @returns The persister
101
+ */
102
+ export declare function createPersister(file: string, initial: PersistedState, options?: {
103
+ debounceMs?: number;
104
+ onError?: (error: unknown) => void;
105
+ }): Persister;
106
+ /**
107
+ * The repository's state entry, created when missing.
108
+ *
109
+ * @param state - The state file
110
+ * @param repoKey - The repository key
111
+ * @returns The entry
112
+ */
113
+ export declare function repoEntry(state: PersistedState, repoKey: string): RepoState;
114
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../../src/tui/state/persist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoBH,4DAA4D;AAC5D,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACtB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACtB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACpC;AAED,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,OAAO,aAAa,CAAC;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACpC;AAED,2BAA2B;AAC3B,wBAAgB,UAAU,IAAI,cAAc,CAE3C;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUjG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAIxE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBzF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAMxE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAmB,GAAG,IAAI,CAU9E;AAED,4BAA4B;AAC5B,MAAM,WAAW,SAAS;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;IAClD,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;IACd,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAO,GAC1E,SAAS,CAgCX;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAO3E"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * The state file — where `e3-ui` remembers the last repository, each
7
+ * repository's last workspace and view, and each value tree's expand-set
8
+ * and top row.
9
+ *
10
+ * Path: `$E3_UI_STATE`, else `$XDG_STATE_HOME/e3-ui/state.json`, else
11
+ * `~/.local/state/e3-ui/state.json` (`%LOCALAPPDATA%\e3-ui\state.json` on
12
+ * Windows). Mode 0600, written atomically (tmp + rename), debounced 250 ms,
13
+ * schema v1 with a `version` guard, tree entries bounded to 200 (LRU), a
14
+ * corrupt file renamed to `.bak` and started fresh. Never tokens — those
15
+ * stay in `~/.e3/credentials.json`.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ import * as fs from 'node:fs';
20
+ import * as path from 'node:path';
21
+ /** The state schema version this build reads and writes. */
22
+ export const STATE_VERSION = 1;
23
+ /** Tree entries kept (least recently used dropped first). */
24
+ export const MAX_TREE_ENTRIES = 200;
25
+ /** Debounce between a change and the write, in milliseconds. */
26
+ export const WRITE_DEBOUNCE_MS = 250;
27
+ /** An empty state file. */
28
+ export function emptyState() {
29
+ return { version: STATE_VERSION, repos: {} };
30
+ }
31
+ /**
32
+ * Resolves the state file path.
33
+ *
34
+ * @param env - The environment
35
+ * @param platform - `process.platform`
36
+ * @param home - The home directory
37
+ * @returns `$E3_UI_STATE`, else `$XDG_STATE_HOME/e3-ui/state.json`, else the platform default
38
+ */
39
+ export function statePath(env, platform, home) {
40
+ const explicit = env['E3_UI_STATE'];
41
+ if (explicit !== undefined && explicit !== '')
42
+ return explicit;
43
+ const xdg = env['XDG_STATE_HOME'];
44
+ if (xdg !== undefined && xdg !== '')
45
+ return path.join(xdg, 'e3-ui', 'state.json');
46
+ if (platform === 'win32') {
47
+ const local = env['LOCALAPPDATA'];
48
+ return path.join(local !== undefined && local !== '' ? local : path.join(home, 'AppData', 'Local'), 'e3-ui', 'state.json');
49
+ }
50
+ return path.join(home, '.local', 'state', 'e3-ui', 'state.json');
51
+ }
52
+ /**
53
+ * Whether a parsed value is a state file this build can read.
54
+ *
55
+ * @param value - The parsed JSON
56
+ * @returns `true` for a v1 object with a `repos` record
57
+ */
58
+ export function isPersistedState(value) {
59
+ if (typeof value !== 'object' || value === null)
60
+ return false;
61
+ const v = value;
62
+ return v['version'] === STATE_VERSION && typeof v['repos'] === 'object' && v['repos'] !== null;
63
+ }
64
+ /**
65
+ * Loads the state file. A missing file starts fresh; a corrupt or
66
+ * incompatible one is renamed to `<file>.bak` and started fresh.
67
+ *
68
+ * @param file - The state file path
69
+ * @returns The state and, when the file was set aside, the backup path
70
+ */
71
+ export function loadState(file) {
72
+ let text;
73
+ try {
74
+ text = fs.readFileSync(file, 'utf8');
75
+ }
76
+ catch {
77
+ return { state: emptyState() };
78
+ }
79
+ try {
80
+ const parsed = JSON.parse(text);
81
+ if (isPersistedState(parsed))
82
+ return { state: parsed };
83
+ }
84
+ catch {
85
+ // fall through to recovery
86
+ }
87
+ const backup = `${file}.bak`;
88
+ try {
89
+ fs.renameSync(file, backup);
90
+ }
91
+ catch {
92
+ // an unreadable file we cannot move: start fresh in memory
93
+ }
94
+ return { state: emptyState(), recoveredFrom: backup };
95
+ }
96
+ /**
97
+ * Writes the state file atomically (temp file + rename) with mode 0600,
98
+ * creating its directory (0700) as needed.
99
+ *
100
+ * @param file - The state file path
101
+ * @param state - The state
102
+ */
103
+ export function writeStateSync(file, state) {
104
+ const dir = path.dirname(file);
105
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
106
+ const tmp = path.join(dir, `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
107
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
108
+ fs.renameSync(tmp, file);
109
+ }
110
+ /**
111
+ * Drops the least recently touched tree entries beyond the cap.
112
+ *
113
+ * @param state - The state (mutated)
114
+ * @param max - The cap
115
+ */
116
+ export function pruneTrees(state, max = MAX_TREE_ENTRIES) {
117
+ const all = [];
118
+ for (const [repo, r] of Object.entries(state.repos)) {
119
+ for (const [key, tree] of Object.entries(r.trees))
120
+ all.push({ repo, key, touched: tree.touched });
121
+ }
122
+ if (all.length <= max)
123
+ return;
124
+ all.sort((a, b) => a.touched - b.touched);
125
+ for (const drop of all.slice(0, all.length - max)) {
126
+ delete state.repos[drop.repo].trees[drop.key];
127
+ }
128
+ }
129
+ /**
130
+ * Creates the debounced writer over a state file.
131
+ *
132
+ * @param file - The state file path
133
+ * @param initial - The loaded state
134
+ * @param options - `debounceMs` (default 250) and `onError` for write failures
135
+ * @returns The persister
136
+ */
137
+ export function createPersister(file, initial, options = {}) {
138
+ const debounceMs = options.debounceMs ?? WRITE_DEBOUNCE_MS;
139
+ const state = initial;
140
+ let dirty = false;
141
+ let timer;
142
+ const write = () => {
143
+ if (timer !== undefined) {
144
+ clearTimeout(timer);
145
+ timer = undefined;
146
+ }
147
+ if (!dirty)
148
+ return;
149
+ dirty = false;
150
+ try {
151
+ pruneTrees(state);
152
+ writeStateSync(file, state);
153
+ }
154
+ catch (error) {
155
+ options.onError?.(error);
156
+ }
157
+ };
158
+ return {
159
+ get state() { return state; },
160
+ get dirty() { return dirty; },
161
+ update(fn) {
162
+ fn(state);
163
+ dirty = true;
164
+ if (timer === undefined) {
165
+ timer = setTimeout(write, debounceMs);
166
+ timer.unref?.();
167
+ }
168
+ },
169
+ flush: write,
170
+ };
171
+ }
172
+ /**
173
+ * The repository's state entry, created when missing.
174
+ *
175
+ * @param state - The state file
176
+ * @param repoKey - The repository key
177
+ * @returns The entry
178
+ */
179
+ export function repoEntry(state, repoKey) {
180
+ let entry = state.repos[repoKey];
181
+ if (entry === undefined) {
182
+ entry = { trees: {} };
183
+ state.repos[repoKey] = entry;
184
+ }
185
+ return entry;
186
+ }
187
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../../../src/tui/state/persist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Pollers — one per data feed, with an interval, an `AbortController`, at
7
+ * most one request in flight, and exponential backoff on consecutive
8
+ * failures (1 → 2 → 4 → 8 → 15 s). A feed runs only while a view that needs
9
+ * it is mounted; the header's connection pill derives from the newest
10
+ * results across the running pollers ({@link connectionState}).
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ /** The backoff ladder after consecutive failures, in milliseconds. */
15
+ export declare const BACKOFF_MS: readonly [1000, 2000, 4000, 8000, 15000];
16
+ /** Failures before the pill reads offline (the ladder's length). */
17
+ export declare const OFFLINE_AFTER: 5;
18
+ /** The timer surface a poller schedules with (injectable for tests). */
19
+ export interface PollClock {
20
+ now(): number;
21
+ setTimeout(fn: () => void, ms: number): unknown;
22
+ clearTimeout(handle: unknown): void;
23
+ }
24
+ /** The real timers. */
25
+ export declare const realClock: PollClock;
26
+ /** What a poller is built from. */
27
+ export interface PollerConfig<T> {
28
+ /** A name for diagnostics. */
29
+ key: string;
30
+ /** The steady-state interval between successful runs, in milliseconds. */
31
+ intervalMs: number;
32
+ /** The request; `signal` aborts when the poller stops or is superseded. */
33
+ run: (signal: AbortSignal) => Promise<T>;
34
+ /** Receives each successful result. */
35
+ onResult: (result: T) => void;
36
+ /** Receives each failure (after which the backoff applies). */
37
+ onError?: ((error: unknown, failures: number) => void) | undefined;
38
+ /** Timers (default: the real ones). */
39
+ clock?: PollClock | undefined;
40
+ }
41
+ /** A poller's observable state. */
42
+ export interface PollerStatus {
43
+ key: string;
44
+ running: boolean;
45
+ inFlight: boolean;
46
+ /** Consecutive failures since the last success. */
47
+ failures: number;
48
+ /** Epoch milliseconds of the last success, or null. */
49
+ lastOkAt: number | null;
50
+ /** Epoch milliseconds of the last attempt (success or failure), or null. */
51
+ lastAttemptAt: number | null;
52
+ }
53
+ /** A running feed. */
54
+ export interface Poller {
55
+ /** Starts polling (the first run is immediate). No-op when running. */
56
+ start(): void;
57
+ /** Stops polling and aborts the request in flight. */
58
+ stop(): void;
59
+ /** Pauses without discarding state (a later `resume` continues). */
60
+ pause(): void;
61
+ /** Resumes a paused poller with an immediate run. */
62
+ resume(): void;
63
+ /** Runs now, regardless of the schedule (a superseded request is ignored). */
64
+ fireNow(): void;
65
+ /** Changes the steady-state interval (takes effect at the next schedule). */
66
+ setInterval(ms: number): void;
67
+ /** The current status. */
68
+ status(): PollerStatus;
69
+ }
70
+ /**
71
+ * Creates a poller.
72
+ *
73
+ * @typeParam T - The result type
74
+ * @param config - The feed
75
+ * @returns The poller (not started)
76
+ */
77
+ export declare function createPoller<T>(config: PollerConfig<T>): Poller;
78
+ /** The header pill's connection state. */
79
+ export type ConnectionState = {
80
+ kind: 'idle';
81
+ } | {
82
+ kind: 'connected';
83
+ } | {
84
+ kind: 'reconnecting';
85
+ attempt: number;
86
+ of: number;
87
+ } | {
88
+ kind: 'offline';
89
+ };
90
+ /**
91
+ * Derives the connection pill from the running pollers: connected while the
92
+ * most recently attempted poller succeeded, reconnecting `n/of` after
93
+ * consecutive failures, offline once the backoff ladder is exhausted.
94
+ *
95
+ * @param pollers - The pollers (their statuses are read)
96
+ * @returns The connection state (`idle` with no running poller)
97
+ */
98
+ export declare function connectionState(pollers: readonly Pick<Poller, 'status'>[]): ConnectionState;
99
+ //# sourceMappingURL=poll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../../../src/tui/state/poll.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,eAAO,MAAM,UAAU,0CAAgD,CAAC;AAExE,oEAAoE;AACpE,eAAO,MAAM,aAAa,GAAoB,CAAC;AAE/C,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACtB,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACvC;AAED,uBAAuB;AACvB,eAAO,MAAM,SAAS,EAAE,SAQvB,CAAC;AAEF,mCAAmC;AACnC,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACzC,uCAAuC;IACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACnE,uCAAuC;IACvC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACjC;AAED,mCAAmC;AACnC,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,sBAAsB;AACtB,MAAM,WAAW,MAAM;IACnB,uEAAuE;IACvE,KAAK,IAAI,IAAI,CAAC;IACd,sDAAsD;IACtD,IAAI,IAAI,IAAI,CAAC;IACb,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAAC;IACd,qDAAqD;IACrD,MAAM,IAAI,IAAI,CAAC;IACf,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAChB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0BAA0B;IAC1B,MAAM,IAAI,YAAY,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,CAoG/D;AAED,0CAA0C;AAC1C,MAAM,MAAM,eAAe,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAE1B;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,eAAe,CAO3F"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Pollers — one per data feed, with an interval, an `AbortController`, at
7
+ * most one request in flight, and exponential backoff on consecutive
8
+ * failures (1 → 2 → 4 → 8 → 15 s). A feed runs only while a view that needs
9
+ * it is mounted; the header's connection pill derives from the newest
10
+ * results across the running pollers ({@link connectionState}).
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ /** The backoff ladder after consecutive failures, in milliseconds. */
15
+ export const BACKOFF_MS = [1_000, 2_000, 4_000, 8_000, 15_000];
16
+ /** Failures before the pill reads offline (the ladder's length). */
17
+ export const OFFLINE_AFTER = BACKOFF_MS.length;
18
+ /** The real timers. */
19
+ export const realClock = {
20
+ now: () => Date.now(),
21
+ setTimeout: (fn, ms) => {
22
+ const handle = setTimeout(fn, ms);
23
+ handle.unref?.();
24
+ return handle;
25
+ },
26
+ clearTimeout: (handle) => clearTimeout(handle),
27
+ };
28
+ /**
29
+ * Creates a poller.
30
+ *
31
+ * @typeParam T - The result type
32
+ * @param config - The feed
33
+ * @returns The poller (not started)
34
+ */
35
+ export function createPoller(config) {
36
+ const clock = config.clock ?? realClock;
37
+ let intervalMs = config.intervalMs;
38
+ let running = false;
39
+ let paused = false;
40
+ let inFlight = false;
41
+ let failures = 0;
42
+ let lastOkAt = null;
43
+ let lastAttemptAt = null;
44
+ let timer = undefined;
45
+ let controller = null;
46
+ let generation = 0;
47
+ const clearTimer = () => {
48
+ if (timer !== undefined) {
49
+ clock.clearTimeout(timer);
50
+ timer = undefined;
51
+ }
52
+ };
53
+ const schedule = (ms) => {
54
+ clearTimer();
55
+ if (!running || paused)
56
+ return;
57
+ timer = clock.setTimeout(() => {
58
+ timer = undefined;
59
+ void tick();
60
+ }, ms);
61
+ };
62
+ const tick = async () => {
63
+ if (!running || paused)
64
+ return;
65
+ if (inFlight)
66
+ return;
67
+ inFlight = true;
68
+ const myGeneration = ++generation;
69
+ controller = new AbortController();
70
+ const signal = controller.signal;
71
+ lastAttemptAt = clock.now();
72
+ try {
73
+ const result = await config.run(signal);
74
+ if (myGeneration !== generation || signal.aborted)
75
+ return;
76
+ inFlight = false;
77
+ failures = 0;
78
+ lastOkAt = clock.now();
79
+ config.onResult(result);
80
+ schedule(intervalMs);
81
+ }
82
+ catch (error) {
83
+ if (myGeneration !== generation || signal.aborted)
84
+ return;
85
+ inFlight = false;
86
+ failures += 1;
87
+ config.onError?.(error, failures);
88
+ schedule(BACKOFF_MS[Math.min(failures, BACKOFF_MS.length) - 1]);
89
+ }
90
+ };
91
+ const abortInFlight = () => {
92
+ if (controller !== null) {
93
+ controller.abort();
94
+ controller = null;
95
+ }
96
+ inFlight = false;
97
+ generation += 1;
98
+ };
99
+ return {
100
+ start() {
101
+ if (running)
102
+ return;
103
+ running = true;
104
+ paused = false;
105
+ void tick();
106
+ },
107
+ stop() {
108
+ running = false;
109
+ paused = false;
110
+ clearTimer();
111
+ abortInFlight();
112
+ },
113
+ pause() {
114
+ if (!running)
115
+ return;
116
+ paused = true;
117
+ clearTimer();
118
+ abortInFlight();
119
+ },
120
+ resume() {
121
+ if (!running || !paused)
122
+ return;
123
+ paused = false;
124
+ void tick();
125
+ },
126
+ fireNow() {
127
+ if (!running || paused)
128
+ return;
129
+ clearTimer();
130
+ abortInFlight();
131
+ void tick();
132
+ },
133
+ setInterval(ms) {
134
+ intervalMs = ms;
135
+ },
136
+ status() {
137
+ return { key: config.key, running: running && !paused, inFlight, failures, lastOkAt, lastAttemptAt };
138
+ },
139
+ };
140
+ }
141
+ /**
142
+ * Derives the connection pill from the running pollers: connected while the
143
+ * most recently attempted poller succeeded, reconnecting `n/of` after
144
+ * consecutive failures, offline once the backoff ladder is exhausted.
145
+ *
146
+ * @param pollers - The pollers (their statuses are read)
147
+ * @returns The connection state (`idle` with no running poller)
148
+ */
149
+ export function connectionState(pollers) {
150
+ const statuses = pollers.map(p => p.status()).filter(s => s.running && s.lastAttemptAt !== null);
151
+ if (statuses.length === 0)
152
+ return { kind: 'idle' };
153
+ const worst = Math.max(...statuses.map(s => s.failures));
154
+ if (worst === 0)
155
+ return { kind: 'connected' };
156
+ if (worst >= OFFLINE_AFTER)
157
+ return { kind: 'offline' };
158
+ return { kind: 'reconnecting', attempt: worst, of: OFFLINE_AFTER - 1 };
159
+ }
160
+ //# sourceMappingURL=poll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll.js","sourceRoot":"","sources":["../../../src/tui/state/poll.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { type Action, type TuiState } from './actions.js';
6
+ /**
7
+ * The reducer.
8
+ *
9
+ * @param state - The current state
10
+ * @param action - The action
11
+ * @returns The next state (the same object when nothing changed)
12
+ */
13
+ export declare function reduce(state: TuiState, action: Action): TuiState;
14
+ /** Whether the state has pending input edits (the `◆ N DIRTY` pill). */
15
+ export declare function dirtyCount(state: TuiState): number;
16
+ //# sourceMappingURL=reducer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../../../src/tui/state/reducer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,OAAO,EACH,KAAK,MAAM,EAIX,KAAK,QAAQ,EAIhB,MAAM,cAAc,CAAC;AAkGtB;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsNhE;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAElD"}