@plures/runebook 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 (148) hide show
  1. package/ANALYSIS_LADDER.md +231 -0
  2. package/CHANGELOG.md +124 -0
  3. package/INTEGRATIONS.md +242 -0
  4. package/LICENSE +21 -0
  5. package/MEMORY.md +253 -0
  6. package/NIXOS.md +357 -0
  7. package/QUICKSTART.md +157 -0
  8. package/README.md +295 -0
  9. package/RELEASE.md +190 -0
  10. package/ValidationChecklist.md +598 -0
  11. package/docs/demo.md +338 -0
  12. package/docs/llm-integration.md +300 -0
  13. package/docs/parallel-execution-plan.md +160 -0
  14. package/flake.nix +228 -0
  15. package/integrations/README.md +242 -0
  16. package/integrations/demo-steps.sh +64 -0
  17. package/integrations/nvim-runebook.lua +140 -0
  18. package/integrations/tmux-status.sh +51 -0
  19. package/integrations/vim-runebook.vim +77 -0
  20. package/integrations/wezterm-status-simple.lua +48 -0
  21. package/integrations/wezterm-status.lua +76 -0
  22. package/nixos-module.nix +156 -0
  23. package/package.json +76 -0
  24. package/packages/design-dojo/index.js +4 -0
  25. package/packages/design-dojo/package.json +20 -0
  26. package/packages/design-dojo/tokens.css +69 -0
  27. package/playwright.config.ts +16 -0
  28. package/scripts/check-versions.cjs +62 -0
  29. package/scripts/demo.sh +220 -0
  30. package/shell.nix +31 -0
  31. package/src/app.html +13 -0
  32. package/src/cli/index.ts +1050 -0
  33. package/src/lib/agent/analysis-pipeline.ts +347 -0
  34. package/src/lib/agent/analysis-service.ts +171 -0
  35. package/src/lib/agent/analysis.ts +159 -0
  36. package/src/lib/agent/analyzers/heuristic.ts +289 -0
  37. package/src/lib/agent/analyzers/index.ts +7 -0
  38. package/src/lib/agent/analyzers/llm.ts +204 -0
  39. package/src/lib/agent/analyzers/local-search.ts +215 -0
  40. package/src/lib/agent/capture.ts +123 -0
  41. package/src/lib/agent/index.ts +244 -0
  42. package/src/lib/agent/integration.ts +81 -0
  43. package/src/lib/agent/llm/providers/base.ts +99 -0
  44. package/src/lib/agent/llm/providers/index.ts +60 -0
  45. package/src/lib/agent/llm/providers/mock.ts +67 -0
  46. package/src/lib/agent/llm/providers/ollama.ts +151 -0
  47. package/src/lib/agent/llm/providers/openai.ts +153 -0
  48. package/src/lib/agent/llm/sanitizer.ts +170 -0
  49. package/src/lib/agent/llm/types.ts +118 -0
  50. package/src/lib/agent/memory.ts +363 -0
  51. package/src/lib/agent/node-status.ts +56 -0
  52. package/src/lib/agent/node-suggestions.ts +64 -0
  53. package/src/lib/agent/status.ts +80 -0
  54. package/src/lib/agent/suggestions.ts +169 -0
  55. package/src/lib/components/Canvas.svelte +124 -0
  56. package/src/lib/components/ConnectionLine.svelte +46 -0
  57. package/src/lib/components/DisplayNode.svelte +167 -0
  58. package/src/lib/components/InputNode.svelte +158 -0
  59. package/src/lib/components/TerminalNode.svelte +237 -0
  60. package/src/lib/components/Toolbar.svelte +359 -0
  61. package/src/lib/components/TransformNode.svelte +327 -0
  62. package/src/lib/core/index.ts +31 -0
  63. package/src/lib/core/observer.ts +278 -0
  64. package/src/lib/core/redaction.ts +158 -0
  65. package/src/lib/core/shell-adapters/base.ts +325 -0
  66. package/src/lib/core/shell-adapters/bash.ts +110 -0
  67. package/src/lib/core/shell-adapters/index.ts +62 -0
  68. package/src/lib/core/shell-adapters/zsh.ts +105 -0
  69. package/src/lib/core/storage.ts +360 -0
  70. package/src/lib/core/types.ts +176 -0
  71. package/src/lib/design-dojo/Box.svelte +47 -0
  72. package/src/lib/design-dojo/Button.svelte +75 -0
  73. package/src/lib/design-dojo/Input.svelte +65 -0
  74. package/src/lib/design-dojo/List.svelte +38 -0
  75. package/src/lib/design-dojo/Select.svelte +48 -0
  76. package/src/lib/design-dojo/SplitPane.svelte +43 -0
  77. package/src/lib/design-dojo/StatusBar.svelte +61 -0
  78. package/src/lib/design-dojo/Table.svelte +47 -0
  79. package/src/lib/design-dojo/Text.svelte +36 -0
  80. package/src/lib/design-dojo/Toggle.svelte +48 -0
  81. package/src/lib/design-dojo/index.ts +10 -0
  82. package/src/lib/stores/canvas-praxis.ts +268 -0
  83. package/src/lib/stores/canvas.ts +58 -0
  84. package/src/lib/types/agent.ts +78 -0
  85. package/src/lib/types/canvas.ts +71 -0
  86. package/src/lib/utils/storage.ts +326 -0
  87. package/src/lib/utils/yaml-loader.ts +52 -0
  88. package/src/routes/+layout.svelte +5 -0
  89. package/src/routes/+layout.ts +5 -0
  90. package/src/routes/+page.svelte +32 -0
  91. package/src-tauri/Cargo.lock +5735 -0
  92. package/src-tauri/Cargo.toml +38 -0
  93. package/src-tauri/build.rs +3 -0
  94. package/src-tauri/capabilities/default.json +10 -0
  95. package/src-tauri/icons/128x128.png +0 -0
  96. package/src-tauri/icons/128x128@2x.png +0 -0
  97. package/src-tauri/icons/32x32.png +0 -0
  98. package/src-tauri/icons/Square107x107Logo.png +0 -0
  99. package/src-tauri/icons/Square142x142Logo.png +0 -0
  100. package/src-tauri/icons/Square150x150Logo.png +0 -0
  101. package/src-tauri/icons/Square284x284Logo.png +0 -0
  102. package/src-tauri/icons/Square30x30Logo.png +0 -0
  103. package/src-tauri/icons/Square310x310Logo.png +0 -0
  104. package/src-tauri/icons/Square44x44Logo.png +0 -0
  105. package/src-tauri/icons/Square71x71Logo.png +0 -0
  106. package/src-tauri/icons/Square89x89Logo.png +0 -0
  107. package/src-tauri/icons/StoreLogo.png +0 -0
  108. package/src-tauri/icons/icon.icns +0 -0
  109. package/src-tauri/icons/icon.ico +0 -0
  110. package/src-tauri/icons/icon.png +0 -0
  111. package/src-tauri/src/agents/agent1.rs +66 -0
  112. package/src-tauri/src/agents/agent2.rs +80 -0
  113. package/src-tauri/src/agents/agent3.rs +73 -0
  114. package/src-tauri/src/agents/agent4.rs +66 -0
  115. package/src-tauri/src/agents/agent5.rs +68 -0
  116. package/src-tauri/src/agents/agent6.rs +75 -0
  117. package/src-tauri/src/agents/base.rs +52 -0
  118. package/src-tauri/src/agents/mod.rs +17 -0
  119. package/src-tauri/src/core/coordination.rs +117 -0
  120. package/src-tauri/src/core/mod.rs +12 -0
  121. package/src-tauri/src/core/ownership.rs +61 -0
  122. package/src-tauri/src/core/types.rs +132 -0
  123. package/src-tauri/src/execution/mod.rs +5 -0
  124. package/src-tauri/src/execution/runner.rs +143 -0
  125. package/src-tauri/src/lib.rs +161 -0
  126. package/src-tauri/src/main.rs +6 -0
  127. package/src-tauri/src/memory/api.rs +422 -0
  128. package/src-tauri/src/memory/client.rs +156 -0
  129. package/src-tauri/src/memory/encryption.rs +79 -0
  130. package/src-tauri/src/memory/migration.rs +110 -0
  131. package/src-tauri/src/memory/mod.rs +28 -0
  132. package/src-tauri/src/memory/schema.rs +275 -0
  133. package/src-tauri/src/memory/tests.rs +192 -0
  134. package/src-tauri/src/orchestrator/coordinator.rs +232 -0
  135. package/src-tauri/src/orchestrator/mod.rs +13 -0
  136. package/src-tauri/src/orchestrator/planner.rs +304 -0
  137. package/src-tauri/tauri.conf.json +35 -0
  138. package/static/examples/date-time-example.yaml +147 -0
  139. package/static/examples/hello-world.yaml +74 -0
  140. package/static/examples/transform-example.yaml +157 -0
  141. package/static/favicon.png +0 -0
  142. package/static/svelte.svg +1 -0
  143. package/static/tauri.svg +6 -0
  144. package/static/vite.svg +1 -0
  145. package/svelte.config.js +18 -0
  146. package/tsconfig.json +19 -0
  147. package/vite.config.js +45 -0
  148. package/vitest.config.ts +21 -0
@@ -0,0 +1,327 @@
1
+ <script lang="ts">
2
+ import type { TransformNode } from '../types/canvas';
3
+ import { canvasStore, nodeDataStore, getNodeInputData, updateNodeData } from '../stores/canvas';
4
+ import Box from '../design-dojo/Box.svelte';
5
+ import Select from '../design-dojo/Select.svelte';
6
+ import Text from '../design-dojo/Text.svelte';
7
+
8
+ interface Props {
9
+ node: TransformNode;
10
+ tui?: boolean;
11
+ }
12
+
13
+ let { node, tui = false }: Props = $props();
14
+
15
+ let output = $state<any>('');
16
+ let error = $state<string>('');
17
+ let isProcessing = $state(false);
18
+
19
+ // Subscribe to node data changes and apply transformation
20
+ $effect(() => {
21
+ const canvas = $canvasStore;
22
+ const nodeData = $nodeDataStore;
23
+
24
+ // Get input data from connected nodes
25
+ if (node.inputs && node.inputs.length > 0) {
26
+ const inputData = getNodeInputData(node.id, node.inputs[0].id, canvas.connections, nodeData);
27
+ if (inputData !== undefined) {
28
+ applyTransform(inputData);
29
+ }
30
+ }
31
+ });
32
+
33
+ async function applyTransform(inputData: any) {
34
+ if (!node.code.trim()) {
35
+ output = inputData;
36
+ updateNodeData(node.id, 'output', inputData);
37
+ return;
38
+ }
39
+
40
+ isProcessing = true;
41
+ error = '';
42
+
43
+ try {
44
+ let result: any;
45
+
46
+ switch (node.transformType) {
47
+ case 'map':
48
+ // Execute JavaScript map function
49
+ if (Array.isArray(inputData)) {
50
+ // Note: Using Function constructor allows user-defined transformations
51
+ // This is intended for local use only. Do not use with untrusted input.
52
+ const mapFn = new Function('item', 'index', `"use strict"; return (${node.code})`);
53
+ result = inputData.map((item, index) => mapFn(item, index));
54
+ } else {
55
+ error = 'Map transform requires array input';
56
+ return;
57
+ }
58
+ break;
59
+
60
+ case 'filter':
61
+ // Execute JavaScript filter function
62
+ if (Array.isArray(inputData)) {
63
+ const filterFn = new Function('item', 'index', `"use strict"; return (${node.code})`);
64
+ result = inputData.filter((item, index) => filterFn(item, index));
65
+ } else {
66
+ error = 'Filter transform requires array input';
67
+ return;
68
+ }
69
+ break;
70
+
71
+ case 'reduce':
72
+ // Execute JavaScript reduce function
73
+ if (Array.isArray(inputData)) {
74
+ if (inputData.length === 0) {
75
+ error = 'Reduce transform requires non-empty array';
76
+ return;
77
+ }
78
+ const reduceFn = new Function('acc', 'item', 'index', `"use strict"; return (${node.code})`);
79
+ // Provide initial value of 0 for safety
80
+ result = inputData.reduce((acc, item, index) => reduceFn(acc, item, index), 0);
81
+ } else {
82
+ error = 'Reduce transform requires array input';
83
+ return;
84
+ }
85
+ break;
86
+
87
+ case 'sudolang':
88
+ // Sudolang is not implemented yet - just pass through
89
+ result = inputData;
90
+ error = 'Sudolang transform not yet implemented - passing through data';
91
+ break;
92
+
93
+ default:
94
+ result = inputData;
95
+ }
96
+
97
+ output = result;
98
+ updateNodeData(node.id, 'output', result);
99
+ } catch (e) {
100
+ error = e instanceof Error ? e.message : String(e);
101
+ output = '';
102
+ } finally {
103
+ isProcessing = false;
104
+ }
105
+ }
106
+
107
+ function updateCode(event: Event) {
108
+ const target = event.target as HTMLTextAreaElement;
109
+ canvasStore.updateNode(node.id, { code: target.value });
110
+ }
111
+
112
+ function updateTransformType(event: Event) {
113
+ const target = event.target as HTMLSelectElement;
114
+ const newType = target.value as TransformNode['transformType'];
115
+ canvasStore.updateNode(node.id, { transformType: newType });
116
+ }
117
+
118
+ function getPlaceholder(type: TransformNode['transformType']): string {
119
+ switch (type) {
120
+ case 'map':
121
+ return 'item * 2';
122
+ case 'filter':
123
+ return 'item > 10';
124
+ case 'reduce':
125
+ return 'acc + item';
126
+ case 'sudolang':
127
+ return '// Sudolang code';
128
+ default:
129
+ return '';
130
+ }
131
+ }
132
+ </script>
133
+
134
+ <Box class="transform-node" surface={2} border radius={3} shadow={2} style="border-color: var(--accent)" {tui}>
135
+ <Box class="node-header" surface={3} {tui}>
136
+ <span class="node-icon">🔄</span>
137
+ <Text class="node-title">{node.label || 'Transform'}</Text>
138
+ </Box>
139
+
140
+ <Box class="node-body" pad={3}>
141
+ <div class="control-group">
142
+ <label for="transform-type-{node.id}">Type:</label>
143
+ <Select
144
+ {tui}
145
+ id="transform-type-{node.id}"
146
+ value={node.transformType}
147
+ onchange={updateTransformType}
148
+ >
149
+ <option value="map">Map</option>
150
+ <option value="filter">Filter</option>
151
+ <option value="reduce">Reduce</option>
152
+ <option value="sudolang">Sudolang (planned)</option>
153
+ </Select>
154
+ </div>
155
+
156
+ <div class="control-group">
157
+ <label for="code-{node.id}">Code:</label>
158
+ <textarea
159
+ id="code-{node.id}"
160
+ value={node.code}
161
+ oninput={updateCode}
162
+ placeholder={getPlaceholder(node.transformType)}
163
+ rows="4"
164
+ class="code-textarea"
165
+ ></textarea>
166
+ </div>
167
+
168
+ {#if error}
169
+ <Box class="error-message" surface={1} pad={2} radius={2}>
170
+ <Text class="error-text">{error}</Text>
171
+ </Box>
172
+ {/if}
173
+
174
+ {#if isProcessing}
175
+ <Box class="status-processing" surface={1} pad={2} radius={2}>
176
+ <Text variant={2}>Processing...</Text>
177
+ </Box>
178
+ {:else if output !== ''}
179
+ <Box class="output-preview" surface={1} pad={2} radius={2}>
180
+ <Text class="output-label">Output:</Text>
181
+ <Text mono variant={1} class="output-pre">{typeof output === 'object' ? JSON.stringify(output, null, 2) : String(output)}</Text>
182
+ </Box>
183
+ {/if}
184
+ </Box>
185
+
186
+ <!-- Input and output ports -->
187
+ <div class="ports">
188
+ {#each node.inputs as port}
189
+ <div class="port input-port" data-port-id={port.id}>
190
+ <span class="port-label">{port.name}</span>
191
+ </div>
192
+ {/each}
193
+
194
+ {#each node.outputs as port}
195
+ <div class="port output-port" data-port-id={port.id}>
196
+ <span class="port-label">{port.name}</span>
197
+ </div>
198
+ {/each}
199
+ </div>
200
+ </Box>
201
+
202
+ <style>
203
+ :global(.transform-node) {
204
+ min-width: 320px;
205
+ max-width: 450px;
206
+ }
207
+
208
+ :global(.transform-node .node-header) {
209
+ padding: var(--space-2) var(--space-3);
210
+ border-bottom: 1px solid var(--border-color);
211
+ display: flex;
212
+ align-items: center;
213
+ gap: var(--space-2);
214
+ border-radius: var(--radius-3) var(--radius-3) 0 0;
215
+ }
216
+
217
+ .node-icon {
218
+ font-size: 18px;
219
+ }
220
+
221
+ :global(.transform-node .node-title) {
222
+ font-weight: 600;
223
+ font-size: var(--font-size-1);
224
+ }
225
+
226
+ :global(.transform-node .node-body) {
227
+ padding: var(--space-3);
228
+ }
229
+
230
+ .control-group {
231
+ margin-bottom: var(--space-3);
232
+ }
233
+
234
+ label {
235
+ display: block;
236
+ margin-bottom: var(--space-1);
237
+ font-size: var(--font-size-0);
238
+ color: var(--text-2);
239
+ }
240
+
241
+ .code-textarea {
242
+ width: 100%;
243
+ padding: var(--space-2);
244
+ background: var(--surface-1);
245
+ border: 1px solid var(--border-color);
246
+ border-radius: var(--radius-2);
247
+ color: var(--text-1);
248
+ font-family: var(--font-mono);
249
+ font-size: var(--font-size-0);
250
+ resize: vertical;
251
+ box-sizing: border-box;
252
+ }
253
+
254
+ .code-textarea::placeholder {
255
+ color: var(--text-3);
256
+ }
257
+
258
+ .code-textarea:focus {
259
+ outline: none;
260
+ border-color: var(--brand);
261
+ }
262
+
263
+ :global(.transform-node .error-message) {
264
+ margin-top: var(--space-2);
265
+ border: 1px solid var(--error);
266
+ }
267
+
268
+ :global(.transform-node .error-text) {
269
+ font-size: var(--font-size-0);
270
+ color: var(--error);
271
+ }
272
+
273
+ :global(.transform-node .status-processing) {
274
+ margin-top: var(--space-2);
275
+ }
276
+
277
+ :global(.transform-node .output-preview) {
278
+ margin-top: var(--space-2);
279
+ max-height: 150px;
280
+ overflow-y: auto;
281
+ }
282
+
283
+ :global(.transform-node .output-label) {
284
+ display: block;
285
+ margin-bottom: var(--space-1);
286
+ font-size: var(--font-size-0);
287
+ font-weight: 600;
288
+ color: var(--brand);
289
+ }
290
+
291
+ :global(.transform-node .output-pre) {
292
+ display: block;
293
+ font-size: var(--font-size-0);
294
+ white-space: pre-wrap;
295
+ word-break: break-word;
296
+ }
297
+
298
+ .ports {
299
+ position: relative;
300
+ }
301
+
302
+ .port {
303
+ position: absolute;
304
+ width: 12px;
305
+ height: 12px;
306
+ background: var(--brand);
307
+ border: 2px solid var(--surface-2);
308
+ border-radius: 50%;
309
+ cursor: crosshair;
310
+ }
311
+
312
+ .input-port {
313
+ left: -8px;
314
+ top: 50%;
315
+ transform: translateY(-50%);
316
+ }
317
+
318
+ .output-port {
319
+ right: -8px;
320
+ top: 50%;
321
+ transform: translateY(-50%);
322
+ }
323
+
324
+ .port-label {
325
+ display: none;
326
+ }
327
+ </style>
@@ -0,0 +1,31 @@
1
+ // RuneBook Core - Terminal Observer Module
2
+ // Main entry point for event capture and observability
3
+
4
+ export { TerminalObserver, createObserver, defaultObserverConfig } from './observer';
5
+ export type { ObserverConfig } from './types';
6
+ export type { TerminalObserverEvent, EventType, ShellType } from './types';
7
+ export { createEventStore } from './storage';
8
+ export type { EventStore } from './storage';
9
+ export { createShellAdapter, detectShellType } from './shell-adapters';
10
+ export type { ShellAdapter } from './shell-adapters';
11
+ export {
12
+ sanitizeEnv,
13
+ redactSecretsFromText,
14
+ isSecretKey,
15
+ redactValue,
16
+ validateRedaction,
17
+ } from './redaction';
18
+
19
+ // Re-export types for convenience
20
+ export type {
21
+ CommandStartEvent,
22
+ CommandEndEvent,
23
+ StdoutChunkEvent,
24
+ StderrChunkEvent,
25
+ ExitStatusEvent,
26
+ CwdChangeEvent,
27
+ EnvChangeEvent,
28
+ SessionStartEvent,
29
+ SessionEndEvent,
30
+ } from './types';
31
+
@@ -0,0 +1,278 @@
1
+ // Terminal Observer - Main coordinator for event capture
2
+ // Orchestrates shell adapters, event storage, and configuration
3
+
4
+ import { createShellAdapter, detectShellType } from './shell-adapters';
5
+ import { createEventStore } from './storage';
6
+ import type {
7
+ ObserverConfig,
8
+ TerminalObserverEvent,
9
+ EventType,
10
+ ShellType,
11
+ } from './types';
12
+ import type { ShellAdapter } from './shell-adapters';
13
+ import type { EventStore } from './storage';
14
+
15
+ // Re-export ObserverConfig for convenience
16
+ export type { ObserverConfig } from './types';
17
+
18
+ export class TerminalObserver {
19
+ private config: ObserverConfig;
20
+ private adapter: ShellAdapter | null = null;
21
+ private store: EventStore | null = null;
22
+ private sessionId: string;
23
+
24
+ constructor(config: Partial<ObserverConfig> = {}) {
25
+ this.sessionId = config.sessionId || `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
26
+
27
+ this.config = {
28
+ enabled: false, // Opt-in by default
29
+ redactSecrets: true,
30
+ usePluresDB: false,
31
+ chunkSize: 4096,
32
+ maxEvents: 10000,
33
+ retentionDays: 30,
34
+ ...config,
35
+ sessionId: this.sessionId,
36
+ shellType: config.shellType || detectShellType(),
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Initialize the observer with storage and shell adapter
42
+ */
43
+ async initialize(): Promise<void> {
44
+ if (!this.config.enabled) {
45
+ return;
46
+ }
47
+
48
+ // Create event store
49
+ this.store = createEventStore(this.config);
50
+
51
+ // Create shell adapter
52
+ this.adapter = createShellAdapter(this.config.shellType);
53
+
54
+ // Initialize adapter
55
+ await this.adapter.initialize(this.config, this.store);
56
+ }
57
+
58
+ /**
59
+ * Start capturing events
60
+ */
61
+ async start(): Promise<void> {
62
+ if (!this.config.enabled) {
63
+ throw new Error('Observer is not enabled. Set enabled: true in config.');
64
+ }
65
+
66
+ if (!this.adapter || !this.store) {
67
+ await this.initialize();
68
+ }
69
+
70
+ if (this.adapter) {
71
+ await this.adapter.start();
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Stop capturing events
77
+ */
78
+ async stop(): Promise<void> {
79
+ if (this.adapter) {
80
+ await this.adapter.stop();
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Check if observer is active
86
+ */
87
+ isActive(): boolean {
88
+ return this.adapter?.isActive() || false;
89
+ }
90
+
91
+ /**
92
+ * Get the shell hook script for manual integration
93
+ */
94
+ getHookScript(): string {
95
+ if (!this.adapter) {
96
+ this.adapter = createShellAdapter(this.config.shellType);
97
+ }
98
+ return this.adapter.getHookScript();
99
+ }
100
+
101
+ /**
102
+ * Programmatically capture a command execution
103
+ * Useful when shell hooks are not available
104
+ */
105
+ async captureCommand(
106
+ command: string,
107
+ args: string[],
108
+ cwd: string,
109
+ env: Record<string, string>
110
+ ): Promise<string> {
111
+ if (!this.config.enabled || !this.adapter || !this.store) {
112
+ throw new Error('Observer not initialized or not enabled');
113
+ }
114
+
115
+ // Use adapter's programmatic capture
116
+ const { BashAdapter } = await import('./shell-adapters/bash.js');
117
+ const { ZshAdapter } = await import('./shell-adapters/zsh.js');
118
+
119
+ if (this.adapter instanceof BashAdapter) {
120
+ return await (this.adapter as any).captureCommand(command, args, cwd, env);
121
+ }
122
+
123
+ if (this.adapter instanceof ZshAdapter) {
124
+ return await (this.adapter as any).captureCommand(command, args, cwd, env);
125
+ }
126
+
127
+ throw new Error('Programmatic capture not supported for this shell adapter');
128
+ }
129
+
130
+ /**
131
+ * Programmatically capture command result
132
+ */
133
+ async captureCommandResult(
134
+ commandId: string,
135
+ stdout: string,
136
+ stderr: string,
137
+ exitCode: number
138
+ ): Promise<void> {
139
+ if (!this.config.enabled || !this.adapter || !this.store) {
140
+ return;
141
+ }
142
+
143
+ const { BashAdapter } = await import('./shell-adapters/bash.js');
144
+ const { ZshAdapter } = await import('./shell-adapters/zsh.js');
145
+
146
+ if (this.adapter instanceof BashAdapter) {
147
+ await (this.adapter as any).captureCommandResult(commandId, stdout, stderr, exitCode);
148
+ return;
149
+ }
150
+
151
+ if (this.adapter instanceof ZshAdapter) {
152
+ await (this.adapter as any).captureCommandResult(commandId, stdout, stderr, exitCode);
153
+ return;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Get events from storage
159
+ */
160
+ async getEvents(
161
+ type?: EventType,
162
+ since?: number,
163
+ limit?: number
164
+ ): Promise<TerminalObserverEvent[]> {
165
+ if (!this.store) {
166
+ await this.initialize();
167
+ }
168
+
169
+ if (!this.store) {
170
+ return [];
171
+ }
172
+
173
+ return await this.store.getEvents(type, since, limit);
174
+ }
175
+
176
+ /**
177
+ * Get events for a specific command
178
+ */
179
+ async getEventsByCommand(commandId: string): Promise<TerminalObserverEvent[]> {
180
+ if (!this.store) {
181
+ await this.initialize();
182
+ }
183
+
184
+ if (!this.store) {
185
+ return [];
186
+ }
187
+
188
+ return await this.store.getEventsByCommand(commandId);
189
+ }
190
+
191
+ /**
192
+ * Get events for current session
193
+ */
194
+ async getEventsBySession(limit?: number): Promise<TerminalObserverEvent[]> {
195
+ if (!this.store) {
196
+ await this.initialize();
197
+ }
198
+
199
+ if (!this.store) {
200
+ return [];
201
+ }
202
+
203
+ return await this.store.getEventsBySession(this.sessionId, limit);
204
+ }
205
+
206
+ /**
207
+ * Get storage statistics
208
+ */
209
+ async getStats() {
210
+ if (!this.store) {
211
+ await this.initialize();
212
+ }
213
+
214
+ if (!this.store) {
215
+ return {
216
+ totalEvents: 0,
217
+ eventsByType: {},
218
+ sessions: 0,
219
+ };
220
+ }
221
+
222
+ return await this.store.getStats();
223
+ }
224
+
225
+ /**
226
+ * Clear old events
227
+ */
228
+ async clearEvents(days?: number): Promise<void> {
229
+ if (!this.store) {
230
+ await this.initialize();
231
+ }
232
+
233
+ if (!this.store) {
234
+ return;
235
+ }
236
+
237
+ const olderThan = days
238
+ ? Date.now() - (days * 24 * 60 * 60 * 1000)
239
+ : undefined;
240
+
241
+ await this.store.clearEvents(olderThan);
242
+ }
243
+
244
+ /**
245
+ * Update configuration
246
+ */
247
+ updateConfig(updates: Partial<ObserverConfig>): void {
248
+ this.config = { ...this.config, ...updates };
249
+ }
250
+
251
+ /**
252
+ * Get current configuration
253
+ */
254
+ getConfig(): ObserverConfig {
255
+ return { ...this.config };
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Create a terminal observer instance
261
+ */
262
+ export function createObserver(config: Partial<ObserverConfig> = {}): TerminalObserver {
263
+ return new TerminalObserver(config);
264
+ }
265
+
266
+ /**
267
+ * Default observer configuration
268
+ */
269
+ export const defaultObserverConfig: ObserverConfig = {
270
+ enabled: false,
271
+ redactSecrets: true,
272
+ usePluresDB: false,
273
+ chunkSize: 4096,
274
+ maxEvents: 10000,
275
+ retentionDays: 30,
276
+ shellType: detectShellType(),
277
+ };
278
+