@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,158 @@
1
+ // Secret redaction utilities for terminal observer
2
+ // Redacts sensitive information from environment variables and output
3
+
4
+ /**
5
+ * Default patterns for secret detection
6
+ */
7
+ const DEFAULT_SECRET_PATTERNS = [
8
+ /token/i,
9
+ /secret/i,
10
+ /password/i,
11
+ /api[_-]?key/i,
12
+ /auth[_-]?token/i,
13
+ /access[_-]?token/i,
14
+ /private[_-]?key/i,
15
+ /credential/i,
16
+ /bearer/i,
17
+ /session[_-]?id/i,
18
+ /cookie/i,
19
+ ];
20
+
21
+ /**
22
+ * Redact a value, replacing it with a placeholder
23
+ * @param value - Value to redact
24
+ * @param fullRedaction - If true, always use [REDACTED]; if false, show first/last 4 chars for long values
25
+ */
26
+ export function redactValue(value: string, fullRedaction: boolean = false): string {
27
+ if (!value || value.length === 0) {
28
+ return value;
29
+ }
30
+
31
+ // If full redaction requested or value is short, use [REDACTED]
32
+ if (fullRedaction || value.length <= 8) {
33
+ return '[REDACTED]';
34
+ }
35
+
36
+ // For longer values in partial redaction mode, show first 4 and last 4 chars
37
+ return `${value.substring(0, 4)}...${value.substring(value.length - 4)}`;
38
+ }
39
+
40
+ /**
41
+ * Check if a key matches secret patterns
42
+ */
43
+ export function isSecretKey(key: string, customPatterns: string[] = []): boolean {
44
+ const allPatterns = [...DEFAULT_SECRET_PATTERNS];
45
+
46
+ // Add custom patterns as regex
47
+ for (const pattern of customPatterns) {
48
+ try {
49
+ allPatterns.push(new RegExp(pattern, 'i'));
50
+ } catch (e) {
51
+ // Invalid regex pattern, skip
52
+ console.warn(`Invalid secret pattern: ${pattern}`);
53
+ }
54
+ }
55
+
56
+ return allPatterns.some(pattern => pattern.test(key));
57
+ }
58
+
59
+ /**
60
+ * Sanitize environment variables by redacting secrets
61
+ */
62
+ export function sanitizeEnv(
63
+ env: Record<string, string>,
64
+ customPatterns: string[] = []
65
+ ): Record<string, string> {
66
+ const sanitized: Record<string, string> = {};
67
+
68
+ for (const [key, value] of Object.entries(env)) {
69
+ if (isSecretKey(key, customPatterns)) {
70
+ // Use full redaction for environment variables
71
+ sanitized[key] = '[REDACTED]';
72
+ } else {
73
+ sanitized[key] = value;
74
+ }
75
+ }
76
+
77
+ return sanitized;
78
+ }
79
+
80
+ /**
81
+ * Redact secrets from a string (for stdout/stderr chunks)
82
+ */
83
+ export function redactSecretsFromText(
84
+ text: string,
85
+ customPatterns: string[] = []
86
+ ): string {
87
+ if (!text) {
88
+ return text;
89
+ }
90
+
91
+ // Common patterns for secrets in output
92
+ const outputPatterns = [
93
+ /(token|secret|password|api[_-]?key|auth[_-]?token|access[_-]?token)\s*[:=]\s*([^\s\n]{8,})/gi,
94
+ /(Bearer|bearer)\s+([A-Za-z0-9\-._~+/]+=*)/g,
95
+ /(-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
96
+ ];
97
+
98
+ let redacted = text;
99
+
100
+ // Apply default patterns
101
+ for (const pattern of outputPatterns) {
102
+ redacted = redacted.replace(pattern, (match, key, value) => {
103
+ if (value) {
104
+ return `${key}=[REDACTED]`;
105
+ }
106
+ return '[REDACTED]';
107
+ });
108
+ }
109
+
110
+ // Apply custom patterns
111
+ for (const pattern of customPatterns) {
112
+ try {
113
+ const regex = new RegExp(pattern, 'gi');
114
+ redacted = redacted.replace(regex, '[REDACTED]');
115
+ } catch (e) {
116
+ // Invalid regex, skip
117
+ console.warn(`Invalid secret pattern: ${pattern}`);
118
+ }
119
+ }
120
+
121
+ return redacted;
122
+ }
123
+
124
+ /**
125
+ * Validate that redaction is working correctly
126
+ */
127
+ export function validateRedaction(): boolean {
128
+ const testEnv = {
129
+ PATH: '/usr/bin:/usr/local/bin',
130
+ HOME: '/home/user',
131
+ API_KEY: 'sk-1234567890abcdef',
132
+ TOKEN: 'secret-token-value',
133
+ NORMAL_VAR: 'normal-value',
134
+ };
135
+
136
+ const sanitized = sanitizeEnv(testEnv);
137
+
138
+ // Check that secrets are redacted
139
+ if (sanitized.API_KEY === testEnv.API_KEY) {
140
+ return false;
141
+ }
142
+
143
+ if (sanitized.TOKEN === testEnv.TOKEN) {
144
+ return false;
145
+ }
146
+
147
+ // Check that non-secrets are preserved
148
+ if (sanitized.PATH !== testEnv.PATH) {
149
+ return false;
150
+ }
151
+
152
+ if (sanitized.HOME !== testEnv.HOME) {
153
+ return false;
154
+ }
155
+
156
+ return true;
157
+ }
158
+
@@ -0,0 +1,325 @@
1
+ // Base shell adapter interface
2
+ // All shell adapters must implement this interface
3
+
4
+ import type {
5
+ TerminalObserverEvent,
6
+ ObserverConfig,
7
+ ShellType,
8
+ CommandStartEvent,
9
+ } from '../types';
10
+ import type { EventStore } from '../storage';
11
+
12
+ export interface ShellAdapter {
13
+ /**
14
+ * Get the shell type this adapter supports
15
+ */
16
+ getShellType(): ShellType;
17
+
18
+ /**
19
+ * Initialize the adapter with config and event store
20
+ */
21
+ initialize(config: ObserverConfig, store: EventStore): Promise<void>;
22
+
23
+ /**
24
+ * Start capturing events
25
+ */
26
+ start(): Promise<void>;
27
+
28
+ /**
29
+ * Stop capturing events
30
+ */
31
+ stop(): Promise<void>;
32
+
33
+ /**
34
+ * Check if the adapter is active
35
+ */
36
+ isActive(): boolean;
37
+
38
+ /**
39
+ * Get shell hook script (for integration into shell init files)
40
+ */
41
+ getHookScript(): string;
42
+ }
43
+
44
+ /**
45
+ * Base implementation with common functionality
46
+ */
47
+ export abstract class BaseShellAdapter implements ShellAdapter {
48
+ protected config: ObserverConfig | null = null;
49
+ protected store: EventStore | null = null;
50
+ protected active = false;
51
+ protected currentCommandId: string | null = null;
52
+ protected commandStartTime: number = 0;
53
+ protected stdoutChunks: string[] = [];
54
+ protected stderrChunks: string[] = [];
55
+ protected stdoutChunkIndex = 0;
56
+ protected stderrChunkIndex = 0;
57
+
58
+ abstract getShellType(): ShellType;
59
+ abstract getHookScript(): string;
60
+
61
+ async initialize(config: ObserverConfig, store: EventStore): Promise<void> {
62
+ this.config = config;
63
+ this.store = store;
64
+ }
65
+
66
+ async start(): Promise<void> {
67
+ if (!this.config || !this.store) {
68
+ throw new Error('Adapter not initialized. Call initialize() first.');
69
+ }
70
+
71
+ if (!this.config.enabled) {
72
+ return;
73
+ }
74
+
75
+ this.active = true;
76
+
77
+ // Emit session start event
78
+ await this.emitSessionStart();
79
+ }
80
+
81
+ async stop(): Promise<void> {
82
+ if (this.active) {
83
+ // Emit session end event
84
+ await this.emitSessionEnd();
85
+ }
86
+ this.active = false;
87
+ }
88
+
89
+ isActive(): boolean {
90
+ return this.active;
91
+ }
92
+
93
+ /**
94
+ * Generate a unique event ID
95
+ */
96
+ protected generateEventId(): string {
97
+ return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
98
+ }
99
+
100
+ /**
101
+ * Generate a unique command ID
102
+ */
103
+ protected generateCommandId(): string {
104
+ return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
105
+ }
106
+
107
+ /**
108
+ * Emit an event to the store
109
+ */
110
+ protected async emitEvent(event: TerminalObserverEvent): Promise<void> {
111
+ if (!this.store) {
112
+ return;
113
+ }
114
+
115
+ await this.store.saveEvent(event);
116
+ }
117
+
118
+ /**
119
+ * Capture command start
120
+ */
121
+ protected async captureCommandStart(
122
+ command: string,
123
+ args: string[],
124
+ cwd: string,
125
+ env: Record<string, string>
126
+ ): Promise<string> {
127
+ if (!this.config || !this.store) {
128
+ throw new Error('Adapter not initialized');
129
+ }
130
+
131
+ const commandId = this.generateCommandId();
132
+ this.currentCommandId = commandId;
133
+ this.commandStartTime = Date.now();
134
+ this.stdoutChunks = [];
135
+ this.stderrChunks = [];
136
+ this.stdoutChunkIndex = 0;
137
+ this.stderrChunkIndex = 0;
138
+
139
+ const { sanitizeEnv } = await import('../redaction.js');
140
+ const envSummary = this.config.redactSecrets
141
+ ? sanitizeEnv(env, this.config.secretPatterns || [])
142
+ : env;
143
+
144
+ const event: CommandStartEvent = {
145
+ id: commandId,
146
+ type: 'command_start',
147
+ timestamp: this.commandStartTime,
148
+ sessionId: this.config.sessionId || 'unknown',
149
+ shellType: this.getShellType(),
150
+ paneId: this.config.paneId,
151
+ tabId: this.config.tabId,
152
+ command,
153
+ args,
154
+ cwd,
155
+ envSummary,
156
+ };
157
+
158
+ await this.emitEvent(event);
159
+ return commandId;
160
+ }
161
+
162
+ /**
163
+ * Capture command end
164
+ */
165
+ protected async captureCommandEnd(commandId: string): Promise<void> {
166
+ if (!this.config || !this.store) {
167
+ return;
168
+ }
169
+
170
+ const duration = Date.now() - this.commandStartTime;
171
+
172
+ await this.emitEvent({
173
+ id: this.generateEventId(),
174
+ type: 'command_end',
175
+ timestamp: Date.now(),
176
+ sessionId: this.config.sessionId || 'unknown',
177
+ shellType: this.getShellType(),
178
+ paneId: this.config.paneId,
179
+ tabId: this.config.tabId,
180
+ commandId,
181
+ duration,
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Capture stdout chunk
187
+ */
188
+ protected async captureStdoutChunk(
189
+ commandId: string,
190
+ chunk: string
191
+ ): Promise<void> {
192
+ if (!this.config || !this.store) {
193
+ return;
194
+ }
195
+
196
+ const { redactSecretsFromText } = await import('../redaction.js');
197
+ const processedChunk = this.config.redactSecrets
198
+ ? redactSecretsFromText(chunk, this.config.secretPatterns || [])
199
+ : chunk;
200
+
201
+ await this.emitEvent({
202
+ id: this.generateEventId(),
203
+ type: 'stdout_chunk',
204
+ timestamp: Date.now(),
205
+ sessionId: this.config.sessionId || 'unknown',
206
+ shellType: this.getShellType(),
207
+ paneId: this.config.paneId,
208
+ tabId: this.config.tabId,
209
+ commandId,
210
+ chunk: processedChunk,
211
+ chunkIndex: this.stdoutChunkIndex++,
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Capture stderr chunk
217
+ */
218
+ protected async captureStderrChunk(
219
+ commandId: string,
220
+ chunk: string
221
+ ): Promise<void> {
222
+ if (!this.config || !this.store) {
223
+ return;
224
+ }
225
+
226
+ const { redactSecretsFromText } = await import('../redaction.js');
227
+ const processedChunk = this.config.redactSecrets
228
+ ? redactSecretsFromText(chunk, this.config.secretPatterns || [])
229
+ : chunk;
230
+
231
+ await this.emitEvent({
232
+ id: this.generateEventId(),
233
+ type: 'stderr_chunk',
234
+ timestamp: Date.now(),
235
+ sessionId: this.config.sessionId || 'unknown',
236
+ shellType: this.getShellType(),
237
+ paneId: this.config.paneId,
238
+ tabId: this.config.tabId,
239
+ commandId,
240
+ chunk: processedChunk,
241
+ chunkIndex: this.stderrChunkIndex++,
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Capture exit status
247
+ */
248
+ protected async captureExitStatus(
249
+ commandId: string,
250
+ exitCode: number
251
+ ): Promise<void> {
252
+ if (!this.config || !this.store) {
253
+ return;
254
+ }
255
+
256
+ await this.emitEvent({
257
+ id: this.generateEventId(),
258
+ type: 'exit_status',
259
+ timestamp: Date.now(),
260
+ sessionId: this.config.sessionId || 'unknown',
261
+ shellType: this.getShellType(),
262
+ paneId: this.config.paneId,
263
+ tabId: this.config.tabId,
264
+ commandId,
265
+ exitCode,
266
+ success: exitCode === 0,
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Emit session start event
272
+ */
273
+ protected async emitSessionStart(): Promise<void> {
274
+ if (!this.config || !this.store) {
275
+ return;
276
+ }
277
+
278
+ const { sanitizeEnv } = await import('../redaction.js');
279
+ const envSummary = this.config.redactSecrets
280
+ ? sanitizeEnv(process.env as Record<string, string>, this.config.secretPatterns || [])
281
+ : (process.env as Record<string, string>);
282
+
283
+ await this.emitEvent({
284
+ id: this.generateEventId(),
285
+ type: 'session_start',
286
+ timestamp: Date.now(),
287
+ sessionId: this.config.sessionId || 'unknown',
288
+ shellType: this.getShellType(),
289
+ paneId: this.config.paneId,
290
+ tabId: this.config.tabId,
291
+ cwd: process.cwd(),
292
+ envSummary,
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Emit session end event
298
+ */
299
+ protected async emitSessionEnd(): Promise<void> {
300
+ if (!this.config || !this.store) {
301
+ return;
302
+ }
303
+
304
+ const sessionStart = this.config.sessionId
305
+ ? await this.store.getEventsBySession(this.config.sessionId, 1)
306
+ : [];
307
+
308
+ const startEvent = sessionStart.find((e: TerminalObserverEvent) => e.type === 'session_start');
309
+ const duration = startEvent
310
+ ? Date.now() - startEvent.timestamp
311
+ : 0;
312
+
313
+ await this.emitEvent({
314
+ id: this.generateEventId(),
315
+ type: 'session_end',
316
+ timestamp: Date.now(),
317
+ sessionId: this.config.sessionId || 'unknown',
318
+ shellType: this.getShellType(),
319
+ paneId: this.config.paneId,
320
+ tabId: this.config.tabId,
321
+ duration,
322
+ });
323
+ }
324
+ }
325
+
@@ -0,0 +1,110 @@
1
+ // @ts-nocheck
2
+ // Bash shell adapter for terminal observer
3
+ // Provides hooks for capturing bash 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 BashAdapter extends BaseShellAdapter {
12
+ private hookScriptPath: string;
13
+
14
+ constructor() {
15
+ super();
16
+ this.hookScriptPath = join(homedir(), '.runebook', 'bash-hook.sh');
17
+ }
18
+
19
+ getShellType(): ShellType {
20
+ return 'bash';
21
+ }
22
+
23
+ getHookScript(): string {
24
+ // @ts-ignore - Shell script content, not TypeScript
25
+ return (
26
+ '# RuneBook Terminal Observer Hook for Bash\n' +
27
+ '# Add this to your ~/.bashrc or ~/.bash_profile\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\n' +
48
+ ' trap \'__runebook_capture_start "$BASH_COMMAND"\' DEBUG\n' +
49
+ ' trap \'__runebook_capture_end\' ERR\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
+ * This can be called from Node.js to capture command execution
69
+ */
70
+ async captureCommand(
71
+ command: string,
72
+ args: string[],
73
+ cwd: string,
74
+ env: Record<string, string>
75
+ ): Promise<string> {
76
+ return await this.captureCommandStart(command, args, cwd, env);
77
+ }
78
+
79
+ /**
80
+ * Programmatic capture of command result
81
+ */
82
+ async captureCommandResult(
83
+ commandId: string,
84
+ stdout: string,
85
+ stderr: string,
86
+ exitCode: number
87
+ ): Promise<void> {
88
+ // Split stdout/stderr into chunks if configured
89
+ const chunkSize = this.config?.chunkSize || 4096;
90
+
91
+ // Capture stdout chunks
92
+ for (let i = 0; i < stdout.length; i += chunkSize) {
93
+ const chunk = stdout.substring(i, i + chunkSize);
94
+ await this.captureStdoutChunk(commandId, chunk);
95
+ }
96
+
97
+ // Capture stderr chunks
98
+ for (let i = 0; i < stderr.length; i += chunkSize) {
99
+ const chunk = stderr.substring(i, i + chunkSize);
100
+ await this.captureStderrChunk(commandId, chunk);
101
+ }
102
+
103
+ // Capture exit status
104
+ await this.captureExitStatus(commandId, exitCode);
105
+
106
+ // Capture command end
107
+ await this.captureCommandEnd(commandId);
108
+ }
109
+ }
110
+
@@ -0,0 +1,62 @@
1
+ // Shell adapter factory and utilities
2
+
3
+ import { BashAdapter } from './bash';
4
+ import { ZshAdapter } from './zsh';
5
+ import type { ShellAdapter } from './base';
6
+ import type { ShellType, ObserverConfig } from '../types';
7
+ import type { EventStore } from '../storage';
8
+
9
+ /**
10
+ * Detect the current shell type
11
+ */
12
+ export function detectShellType(): ShellType {
13
+ const shell = process.env.SHELL || '';
14
+
15
+ if (shell.includes('bash')) {
16
+ return 'bash';
17
+ }
18
+
19
+ if (shell.includes('zsh')) {
20
+ return 'zsh';
21
+ }
22
+
23
+ if (shell.includes('nu')) {
24
+ return 'nushell';
25
+ }
26
+
27
+ return 'unknown';
28
+ }
29
+
30
+ /**
31
+ * Create a shell adapter for the specified shell type
32
+ */
33
+ export function createShellAdapter(shellType?: ShellType): ShellAdapter {
34
+ const type = shellType || detectShellType();
35
+
36
+ switch (type) {
37
+ case 'bash':
38
+ return new BashAdapter();
39
+ case 'zsh':
40
+ return new ZshAdapter();
41
+ case 'nushell':
42
+ // TODO: Implement nushell adapter
43
+ throw new Error('Nushell adapter not yet implemented');
44
+ default:
45
+ throw new Error(`Unsupported shell type: ${type}`);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get all available shell adapters
51
+ */
52
+ export function getAvailableAdapters(): ShellAdapter[] {
53
+ return [
54
+ new BashAdapter(),
55
+ new ZshAdapter(),
56
+ // Nushell adapter will be added later
57
+ ];
58
+ }
59
+
60
+ export { BashAdapter, ZshAdapter };
61
+ export type { ShellAdapter } from './base';
62
+