@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,105 @@
1
+ // @ts-nocheck
2
+ // Zsh shell adapter for terminal observer
3
+ // Provides hooks for capturing zsh shell events
4
+
5
+ import { BaseShellAdapter } from './base';
6
+ import type { ShellType, ObserverConfig } from '../types';
7
+ import type { EventStore } from '../storage';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+
11
+ export class ZshAdapter extends BaseShellAdapter {
12
+ private hookScriptPath: string;
13
+
14
+ constructor() {
15
+ super();
16
+ this.hookScriptPath = join(homedir(), '.runebook', 'zsh-hook.sh');
17
+ }
18
+
19
+ getShellType(): ShellType {
20
+ return 'zsh';
21
+ }
22
+
23
+ getHookScript(): string {
24
+ // @ts-ignore - Shell script content, not TypeScript
25
+ return (
26
+ '# RuneBook Terminal Observer Hook for Zsh\n' +
27
+ '# Add this to your ~/.zshrc\n\n' +
28
+ 'if [ -n "$RUNBOOK_OBSERVER_ENABLED" ]; then\n' +
29
+ ' # Function to capture command start\n' +
30
+ ' __runebook_capture_start() {\n' +
31
+ ' local cmd="$1"\n' +
32
+ ' local args="${@:2}"\n' +
33
+ ' local cwd="$PWD"\n \n' +
34
+ ' # Call runebook observer API (if available)\n' +
35
+ ' if command -v runebook >/dev/null 2>&1; then\n' +
36
+ ' runebook observer capture-start "$cmd" "$args" "$cwd" &\n' +
37
+ ' fi\n' +
38
+ ' }\n\n' +
39
+ ' # Function to capture command end\n' +
40
+ ' __runebook_capture_end() {\n' +
41
+ ' local exit_code=$?\n \n' +
42
+ ' if command -v runebook >/dev/null 2>&1; then\n' +
43
+ ' runebook observer capture-end $exit_code &\n' +
44
+ ' fi\n \n' +
45
+ ' return $exit_code\n' +
46
+ ' }\n\n' +
47
+ ' # Hook into command execution using zsh hooks\n' +
48
+ ' preexec_functions+=(__runebook_capture_start)\n' +
49
+ ' precmd_functions+=(__runebook_capture_end)\n' +
50
+ 'fi\n'
51
+ );
52
+ }
53
+
54
+ async initialize(config: ObserverConfig, store: EventStore): Promise<void> {
55
+ await super.initialize(config, store);
56
+ }
57
+
58
+ async start(): Promise<void> {
59
+ await super.start();
60
+ }
61
+
62
+ async stop(): Promise<void> {
63
+ await super.stop();
64
+ }
65
+
66
+ /**
67
+ * Programmatic capture - for use when shell hooks are not available
68
+ */
69
+ async captureCommand(
70
+ command: string,
71
+ args: string[],
72
+ cwd: string,
73
+ env: Record<string, string>
74
+ ): Promise<string> {
75
+ return await this.captureCommandStart(command, args, cwd, env);
76
+ }
77
+
78
+ /**
79
+ * Programmatic capture of command result
80
+ */
81
+ async captureCommandResult(
82
+ commandId: string,
83
+ stdout: string,
84
+ stderr: string,
85
+ exitCode: number
86
+ ): Promise<void> {
87
+ const chunkSize = this.config?.chunkSize || 4096;
88
+
89
+ // Capture stdout chunks
90
+ for (let i = 0; i < stdout.length; i += chunkSize) {
91
+ const chunk = stdout.substring(i, i + chunkSize);
92
+ await this.captureStdoutChunk(commandId, chunk);
93
+ }
94
+
95
+ // Capture stderr chunks
96
+ for (let i = 0; i < stderr.length; i += chunkSize) {
97
+ const chunk = stderr.substring(i, i + chunkSize);
98
+ await this.captureStderrChunk(commandId, chunk);
99
+ }
100
+
101
+ await this.captureExitStatus(commandId, exitCode);
102
+ await this.captureCommandEnd(commandId);
103
+ }
104
+ }
105
+
@@ -0,0 +1,360 @@
1
+ // Event storage layer for terminal observer
2
+ // Supports both PluresDB and local file-based storage
3
+
4
+ import type {
5
+ TerminalObserverEvent,
6
+ EventStore,
7
+ EventType,
8
+ ObserverConfig,
9
+ } from './types';
10
+ import { mkdir, readFile, writeFile } from 'fs/promises';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+ import { existsSync } from 'fs';
14
+
15
+ // Re-export EventStore from types for convenience
16
+ export type { EventStore } from './types';
17
+
18
+ /**
19
+ * Local file-based storage adapter
20
+ * Stores events as JSON files in a directory
21
+ */
22
+ export class LocalFileStore implements EventStore {
23
+ private events: TerminalObserverEvent[] = [];
24
+ private config: ObserverConfig;
25
+ private initialized = false;
26
+ private storagePath: string;
27
+ private eventsFile: string;
28
+ private writePromise: Promise<void> | null = null;
29
+
30
+ constructor(config: ObserverConfig) {
31
+ this.config = config;
32
+ this.storagePath = config.storagePath || join(homedir(), '.runebook', 'observer');
33
+ this.eventsFile = join(this.storagePath, 'events.json');
34
+ }
35
+
36
+ private async ensureInitialized(): Promise<void> {
37
+ if (this.initialized) {
38
+ return;
39
+ }
40
+
41
+ // Create storage directory (mkdir with recursive handles already exists)
42
+ try {
43
+ await mkdir(this.storagePath, { recursive: true });
44
+ } catch (error) {
45
+ // Directory already exists or can't be created
46
+ console.error('Failed to create storage directory:', error);
47
+ }
48
+
49
+ // Load existing events from file (handle ENOENT directly)
50
+ try {
51
+ const data = await readFile(this.eventsFile, 'utf-8');
52
+ this.events = JSON.parse(data);
53
+ } catch (error: unknown) {
54
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
55
+ // File doesn't exist yet, start with empty array
56
+ this.events = [];
57
+ } else {
58
+ console.error('Failed to load events from file:', error);
59
+ this.events = [];
60
+ }
61
+ }
62
+
63
+ this.initialized = true;
64
+ }
65
+
66
+ private async persistEvents(): Promise<void> {
67
+ // Serialize writes to prevent race conditions and data corruption
68
+ const writeOp = (async () => {
69
+ // If there's already a write in progress, wait for it
70
+ while (this.writePromise) {
71
+ await this.writePromise;
72
+ }
73
+
74
+ // Now perform this write
75
+ try {
76
+ await writeFile(this.eventsFile, JSON.stringify(this.events, null, 2), 'utf-8');
77
+ } catch (error) {
78
+ console.error('Failed to persist events to file:', error);
79
+ }
80
+ })();
81
+
82
+ this.writePromise = writeOp;
83
+ await writeOp;
84
+
85
+ // Clear writePromise only if it's still this operation
86
+ if (this.writePromise === writeOp) {
87
+ this.writePromise = null;
88
+ }
89
+ }
90
+
91
+ async saveEvent(event: TerminalObserverEvent): Promise<void> {
92
+ await this.ensureInitialized();
93
+
94
+ this.events.push(event);
95
+
96
+ // Enforce max events limit
97
+ if (this.config.maxEvents && this.config.maxEvents > 0) {
98
+ if (this.events.length > this.config.maxEvents) {
99
+ // Remove oldest events
100
+ this.events = this.events.slice(-this.config.maxEvents);
101
+ }
102
+ }
103
+
104
+ // Persist to file
105
+ await this.persistEvents();
106
+ }
107
+
108
+ async getEvents(
109
+ type?: EventType,
110
+ since?: number,
111
+ limit?: number
112
+ ): Promise<TerminalObserverEvent[]> {
113
+ await this.ensureInitialized();
114
+
115
+ let filtered = this.events;
116
+
117
+ if (type) {
118
+ filtered = filtered.filter(e => e.type === type);
119
+ }
120
+
121
+ if (since) {
122
+ filtered = filtered.filter(e => e.timestamp >= since);
123
+ }
124
+
125
+ // Sort by timestamp (newest first)
126
+ filtered.sort((a, b) => b.timestamp - a.timestamp);
127
+
128
+ if (limit && limit > 0) {
129
+ filtered = filtered.slice(0, limit);
130
+ }
131
+
132
+ return filtered;
133
+ }
134
+
135
+ async getEventsByCommand(
136
+ commandId: string
137
+ ): Promise<TerminalObserverEvent[]> {
138
+ await this.ensureInitialized();
139
+
140
+ return this.events.filter(e => {
141
+ // command_start events use their id as the command identifier
142
+ if (e.type === 'command_start' && e.id === commandId) {
143
+ return true;
144
+ }
145
+ // Other events reference the command via commandId field
146
+ if ('commandId' in e && e.commandId === commandId) {
147
+ return true;
148
+ }
149
+ return false;
150
+ }).sort((a, b) => a.timestamp - b.timestamp);
151
+ }
152
+
153
+ async getEventsBySession(
154
+ sessionId: string,
155
+ limit?: number
156
+ ): Promise<TerminalObserverEvent[]> {
157
+ await this.ensureInitialized();
158
+
159
+ let filtered = this.events.filter(e => e.sessionId === sessionId);
160
+
161
+ filtered.sort((a, b) => b.timestamp - a.timestamp);
162
+
163
+ if (limit && limit > 0) {
164
+ filtered = filtered.slice(0, limit);
165
+ }
166
+
167
+ return filtered;
168
+ }
169
+
170
+ async clearEvents(olderThan?: number): Promise<void> {
171
+ await this.ensureInitialized();
172
+
173
+ if (olderThan) {
174
+ this.events = this.events.filter(e => e.timestamp >= olderThan);
175
+ } else {
176
+ this.events = [];
177
+ }
178
+
179
+ // Persist changes to file
180
+ await this.persistEvents();
181
+ }
182
+
183
+ async getStats(): Promise<{
184
+ totalEvents: number;
185
+ eventsByType: Record<EventType, number>;
186
+ sessions: number;
187
+ }> {
188
+ await this.ensureInitialized();
189
+
190
+ const eventsByType: Record<string, number> = {};
191
+ const sessions = new Set<string>();
192
+
193
+ for (const event of this.events) {
194
+ eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
195
+ sessions.add(event.sessionId);
196
+ }
197
+
198
+ return {
199
+ totalEvents: this.events.length,
200
+ eventsByType: eventsByType as Record<EventType, number>,
201
+ sessions: sessions.size,
202
+ };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * PluresDB storage adapter
208
+ */
209
+ export class PluresDBEventStore implements EventStore {
210
+ private db: any = null;
211
+ private readonly eventPrefix = 'observer:event:';
212
+ private initialized = false;
213
+ private config: ObserverConfig;
214
+
215
+ constructor(config: ObserverConfig) {
216
+ this.config = config;
217
+ }
218
+
219
+ private async ensureInitialized(): Promise<void> {
220
+ if (this.initialized && this.db) {
221
+ return;
222
+ }
223
+
224
+ try {
225
+ const { SQLiteCompatibleAPI } = await import('pluresdb');
226
+
227
+ this.db = new SQLiteCompatibleAPI({
228
+ config: {
229
+ port: 34567,
230
+ host: 'localhost',
231
+ dataDir: this.config.storagePath || './pluresdb-data',
232
+ },
233
+ autoStart: true,
234
+ });
235
+
236
+ await this.db.start();
237
+ this.initialized = true;
238
+ } catch (error) {
239
+ console.error('Failed to initialize PluresDB for observer storage:', error);
240
+ throw new Error('PluresDB initialization failed for observer storage');
241
+ }
242
+ }
243
+
244
+ async saveEvent(event: TerminalObserverEvent): Promise<void> {
245
+ await this.ensureInitialized();
246
+ const key = `${this.eventPrefix}${event.id}`;
247
+ await this.db.put(key, event);
248
+ }
249
+
250
+ async getEvents(
251
+ type?: EventType,
252
+ since?: number,
253
+ limit?: number
254
+ ): Promise<TerminalObserverEvent[]> {
255
+ await this.ensureInitialized();
256
+ const keys = await this.db.list(this.eventPrefix);
257
+ const events: TerminalObserverEvent[] = [];
258
+
259
+ for (const key of keys) {
260
+ try {
261
+ const event = await this.db.getValue(key);
262
+ if (event) {
263
+ if (type && event.type !== type) {
264
+ continue;
265
+ }
266
+ if (since && event.timestamp < since) {
267
+ continue;
268
+ }
269
+ events.push(event as TerminalObserverEvent);
270
+ }
271
+ } catch (error) {
272
+ console.error('Failed to load event:', error);
273
+ }
274
+ }
275
+
276
+ events.sort((a, b) => b.timestamp - a.timestamp);
277
+ return limit && limit > 0 ? events.slice(0, limit) : events;
278
+ }
279
+
280
+ async getEventsByCommand(
281
+ commandId: string
282
+ ): Promise<TerminalObserverEvent[]> {
283
+ const allEvents = await this.getEvents();
284
+ return allEvents.filter(e => {
285
+ // command_start events use their id as the command identifier
286
+ if (e.type === 'command_start' && e.id === commandId) {
287
+ return true;
288
+ }
289
+ // Other events reference the command via commandId field
290
+ if ('commandId' in e && e.commandId === commandId) {
291
+ return true;
292
+ }
293
+ return false;
294
+ }).sort((a, b) => a.timestamp - b.timestamp);
295
+ }
296
+
297
+ async getEventsBySession(
298
+ sessionId: string,
299
+ limit?: number
300
+ ): Promise<TerminalObserverEvent[]> {
301
+ const allEvents = await this.getEvents();
302
+ let filtered = allEvents.filter(e => e.sessionId === sessionId);
303
+
304
+ filtered.sort((a, b) => b.timestamp - a.timestamp);
305
+
306
+ if (limit && limit > 0) {
307
+ filtered = filtered.slice(0, limit);
308
+ }
309
+
310
+ return filtered;
311
+ }
312
+
313
+ async clearEvents(olderThan?: number): Promise<void> {
314
+ await this.ensureInitialized();
315
+ const keys = await this.db.list(this.eventPrefix);
316
+
317
+ for (const key of keys) {
318
+ try {
319
+ const event = await this.db.getValue(key);
320
+ if (event && (!olderThan || event.timestamp < olderThan)) {
321
+ await this.db.delete(key);
322
+ }
323
+ } catch (error) {
324
+ console.error('Failed to delete event:', error);
325
+ }
326
+ }
327
+ }
328
+
329
+ async getStats(): Promise<{
330
+ totalEvents: number;
331
+ eventsByType: Record<EventType, number>;
332
+ sessions: number;
333
+ }> {
334
+ const events = await this.getEvents();
335
+ const eventsByType: Record<string, number> = {};
336
+ const sessions = new Set<string>();
337
+
338
+ for (const event of events) {
339
+ eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
340
+ sessions.add(event.sessionId);
341
+ }
342
+
343
+ return {
344
+ totalEvents: events.length,
345
+ eventsByType: eventsByType as Record<EventType, number>,
346
+ sessions: sessions.size,
347
+ };
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Create event store based on config
353
+ */
354
+ export function createEventStore(config: ObserverConfig): EventStore {
355
+ if (config.usePluresDB && config.storagePath) {
356
+ return new PluresDBEventStore(config);
357
+ }
358
+ return new LocalFileStore(config);
359
+ }
360
+
@@ -0,0 +1,176 @@
1
+ // Canonical event schema for terminal observer layer
2
+ // Low-level shell event capture
3
+
4
+ export type ShellType = 'bash' | 'zsh' | 'nushell' | 'unknown';
5
+
6
+ export type EventType =
7
+ | 'command_start'
8
+ | 'command_end'
9
+ | 'stdout_chunk'
10
+ | 'stderr_chunk'
11
+ | 'exit_status'
12
+ | 'cwd_change'
13
+ | 'env_change'
14
+ | 'session_start'
15
+ | 'session_end';
16
+
17
+ /**
18
+ * Base event structure for all terminal observer events
19
+ */
20
+ export interface BaseTerminalEvent {
21
+ id: string;
22
+ type: EventType;
23
+ timestamp: number;
24
+ sessionId: string;
25
+ shellType: ShellType;
26
+ paneId?: string; // Terminal pane/tab identifier
27
+ tabId?: string; // Terminal tab identifier (if applicable)
28
+ }
29
+
30
+ /**
31
+ * Command start event - fired when a command begins execution
32
+ */
33
+ export interface CommandStartEvent extends BaseTerminalEvent {
34
+ type: 'command_start';
35
+ command: string;
36
+ args: string[];
37
+ cwd: string;
38
+ envSummary: Record<string, string>; // Sanitized environment variables
39
+ pid?: number; // Process ID if available
40
+ }
41
+
42
+ /**
43
+ * Command end event - fired when a command completes
44
+ */
45
+ export interface CommandEndEvent extends BaseTerminalEvent {
46
+ type: 'command_end';
47
+ commandId: string; // Reference to command_start event
48
+ duration: number; // Milliseconds
49
+ }
50
+
51
+ /**
52
+ * Stdout chunk event - incremental stdout output
53
+ */
54
+ export interface StdoutChunkEvent extends BaseTerminalEvent {
55
+ type: 'stdout_chunk';
56
+ commandId: string; // Reference to command_start event
57
+ chunk: string;
58
+ chunkIndex: number; // Sequential chunk number for this command
59
+ }
60
+
61
+ /**
62
+ * Stderr chunk event - incremental stderr output
63
+ */
64
+ export interface StderrChunkEvent extends BaseTerminalEvent {
65
+ type: 'stderr_chunk';
66
+ commandId: string; // Reference to command_start event
67
+ chunk: string;
68
+ chunkIndex: number; // Sequential chunk number for this command
69
+ }
70
+
71
+ /**
72
+ * Exit status event - command exit code
73
+ */
74
+ export interface ExitStatusEvent extends BaseTerminalEvent {
75
+ type: 'exit_status';
76
+ commandId: string; // Reference to command_start event
77
+ exitCode: number;
78
+ success: boolean;
79
+ }
80
+
81
+ /**
82
+ * CWD change event - working directory changed
83
+ */
84
+ export interface CwdChangeEvent extends BaseTerminalEvent {
85
+ type: 'cwd_change';
86
+ cwd: string;
87
+ previousCwd?: string;
88
+ }
89
+
90
+ /**
91
+ * Environment change event - environment variables changed
92
+ */
93
+ export interface EnvChangeEvent extends BaseTerminalEvent {
94
+ type: 'env_change';
95
+ envSummary: Record<string, string>; // Sanitized environment variables
96
+ changedKeys: string[]; // Keys that were added/modified
97
+ }
98
+
99
+ /**
100
+ * Session start event
101
+ */
102
+ export interface SessionStartEvent extends BaseTerminalEvent {
103
+ type: 'session_start';
104
+ shellType: ShellType;
105
+ cwd: string;
106
+ envSummary: Record<string, string>;
107
+ }
108
+
109
+ /**
110
+ * Session end event
111
+ */
112
+ export interface SessionEndEvent extends BaseTerminalEvent {
113
+ type: 'session_end';
114
+ duration: number; // Session duration in milliseconds
115
+ }
116
+
117
+ /**
118
+ * Union type for all terminal observer events
119
+ */
120
+ export type TerminalObserverEvent =
121
+ | CommandStartEvent
122
+ | CommandEndEvent
123
+ | StdoutChunkEvent
124
+ | StderrChunkEvent
125
+ | ExitStatusEvent
126
+ | CwdChangeEvent
127
+ | EnvChangeEvent
128
+ | SessionStartEvent
129
+ | SessionEndEvent;
130
+
131
+ // Re-export LLMProviderConfig from agent/llm/types
132
+ export type { LLMProviderConfig } from '../agent/llm/types';
133
+
134
+ /**
135
+ * Configuration for terminal observer
136
+ */
137
+ export interface ObserverConfig {
138
+ enabled: boolean; // Opt-in flag
139
+ shellType?: ShellType; // Auto-detect if not specified
140
+ sessionId?: string; // Auto-generate if not specified
141
+ paneId?: string;
142
+ tabId?: string;
143
+ storagePath?: string; // Path for local storage or PluresDB
144
+ usePluresDB?: boolean; // Use PluresDB if available, otherwise local store
145
+ redactSecrets: boolean; // Enable secret redaction
146
+ secretPatterns?: string[]; // Additional patterns to redact
147
+ chunkSize?: number; // Max size for stdout/stderr chunks (bytes)
148
+ maxEvents?: number; // Maximum events to store (0 = unlimited)
149
+ retentionDays?: number; // Days to retain events (0 = unlimited)
150
+ }
151
+
152
+ /**
153
+ * Event storage interface
154
+ */
155
+ export interface EventStore {
156
+ saveEvent(event: TerminalObserverEvent): Promise<void>;
157
+ getEvents(
158
+ type?: EventType,
159
+ since?: number,
160
+ limit?: number
161
+ ): Promise<TerminalObserverEvent[]>;
162
+ getEventsByCommand(
163
+ commandId: string
164
+ ): Promise<TerminalObserverEvent[]>;
165
+ getEventsBySession(
166
+ sessionId: string,
167
+ limit?: number
168
+ ): Promise<TerminalObserverEvent[]>;
169
+ clearEvents(olderThan?: number): Promise<void>;
170
+ getStats(): Promise<{
171
+ totalEvents: number;
172
+ eventsByType: Record<EventType, number>;
173
+ sessions: number;
174
+ }>;
175
+ }
176
+
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ tui?: boolean;
6
+ surface?: 1 | 2 | 3 | 4;
7
+ border?: boolean;
8
+ pad?: 1 | 2 | 3 | 4 | 5 | 6;
9
+ radius?: 1 | 2 | 3 | 4;
10
+ shadow?: 1 | 2 | 3;
11
+ class?: string;
12
+ style?: string;
13
+ children?: Snippet;
14
+ }
15
+
16
+ let {
17
+ tui = false,
18
+ surface,
19
+ border = false,
20
+ pad,
21
+ radius,
22
+ shadow,
23
+ class: cls = '',
24
+ style = '',
25
+ children
26
+ }: Props = $props();
27
+
28
+ const inlineStyle = $derived([
29
+ surface != null ? `background: var(--surface-${surface})` : null,
30
+ border ? `border: 1px solid var(--border-color)` : null,
31
+ pad != null ? `padding: var(--space-${pad})` : null,
32
+ radius != null ? `border-radius: var(--radius-${radius})` : null,
33
+ shadow != null ? `box-shadow: var(--shadow-${shadow})` : null,
34
+ style || null,
35
+ ].filter(Boolean).join('; '));
36
+ </script>
37
+
38
+ <div class="dd-box {cls}" style={inlineStyle} data-tui={tui}>
39
+ {@render children?.()}
40
+ </div>
41
+
42
+ <style>
43
+ .dd-box {
44
+ color: var(--text-1);
45
+ box-sizing: border-box;
46
+ }
47
+ </style>