@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.
- package/ANALYSIS_LADDER.md +231 -0
- package/CHANGELOG.md +124 -0
- package/INTEGRATIONS.md +242 -0
- package/LICENSE +21 -0
- package/MEMORY.md +253 -0
- package/NIXOS.md +357 -0
- package/QUICKSTART.md +157 -0
- package/README.md +295 -0
- package/RELEASE.md +190 -0
- package/ValidationChecklist.md +598 -0
- package/docs/demo.md +338 -0
- package/docs/llm-integration.md +300 -0
- package/docs/parallel-execution-plan.md +160 -0
- package/flake.nix +228 -0
- package/integrations/README.md +242 -0
- package/integrations/demo-steps.sh +64 -0
- package/integrations/nvim-runebook.lua +140 -0
- package/integrations/tmux-status.sh +51 -0
- package/integrations/vim-runebook.vim +77 -0
- package/integrations/wezterm-status-simple.lua +48 -0
- package/integrations/wezterm-status.lua +76 -0
- package/nixos-module.nix +156 -0
- package/package.json +76 -0
- package/packages/design-dojo/index.js +4 -0
- package/packages/design-dojo/package.json +20 -0
- package/packages/design-dojo/tokens.css +69 -0
- package/playwright.config.ts +16 -0
- package/scripts/check-versions.cjs +62 -0
- package/scripts/demo.sh +220 -0
- package/shell.nix +31 -0
- package/src/app.html +13 -0
- package/src/cli/index.ts +1050 -0
- package/src/lib/agent/analysis-pipeline.ts +347 -0
- package/src/lib/agent/analysis-service.ts +171 -0
- package/src/lib/agent/analysis.ts +159 -0
- package/src/lib/agent/analyzers/heuristic.ts +289 -0
- package/src/lib/agent/analyzers/index.ts +7 -0
- package/src/lib/agent/analyzers/llm.ts +204 -0
- package/src/lib/agent/analyzers/local-search.ts +215 -0
- package/src/lib/agent/capture.ts +123 -0
- package/src/lib/agent/index.ts +244 -0
- package/src/lib/agent/integration.ts +81 -0
- package/src/lib/agent/llm/providers/base.ts +99 -0
- package/src/lib/agent/llm/providers/index.ts +60 -0
- package/src/lib/agent/llm/providers/mock.ts +67 -0
- package/src/lib/agent/llm/providers/ollama.ts +151 -0
- package/src/lib/agent/llm/providers/openai.ts +153 -0
- package/src/lib/agent/llm/sanitizer.ts +170 -0
- package/src/lib/agent/llm/types.ts +118 -0
- package/src/lib/agent/memory.ts +363 -0
- package/src/lib/agent/node-status.ts +56 -0
- package/src/lib/agent/node-suggestions.ts +64 -0
- package/src/lib/agent/status.ts +80 -0
- package/src/lib/agent/suggestions.ts +169 -0
- package/src/lib/components/Canvas.svelte +124 -0
- package/src/lib/components/ConnectionLine.svelte +46 -0
- package/src/lib/components/DisplayNode.svelte +167 -0
- package/src/lib/components/InputNode.svelte +158 -0
- package/src/lib/components/TerminalNode.svelte +237 -0
- package/src/lib/components/Toolbar.svelte +359 -0
- package/src/lib/components/TransformNode.svelte +327 -0
- package/src/lib/core/index.ts +31 -0
- package/src/lib/core/observer.ts +278 -0
- package/src/lib/core/redaction.ts +158 -0
- package/src/lib/core/shell-adapters/base.ts +325 -0
- package/src/lib/core/shell-adapters/bash.ts +110 -0
- package/src/lib/core/shell-adapters/index.ts +62 -0
- package/src/lib/core/shell-adapters/zsh.ts +105 -0
- package/src/lib/core/storage.ts +360 -0
- package/src/lib/core/types.ts +176 -0
- package/src/lib/design-dojo/Box.svelte +47 -0
- package/src/lib/design-dojo/Button.svelte +75 -0
- package/src/lib/design-dojo/Input.svelte +65 -0
- package/src/lib/design-dojo/List.svelte +38 -0
- package/src/lib/design-dojo/Select.svelte +48 -0
- package/src/lib/design-dojo/SplitPane.svelte +43 -0
- package/src/lib/design-dojo/StatusBar.svelte +61 -0
- package/src/lib/design-dojo/Table.svelte +47 -0
- package/src/lib/design-dojo/Text.svelte +36 -0
- package/src/lib/design-dojo/Toggle.svelte +48 -0
- package/src/lib/design-dojo/index.ts +10 -0
- package/src/lib/stores/canvas-praxis.ts +268 -0
- package/src/lib/stores/canvas.ts +58 -0
- package/src/lib/types/agent.ts +78 -0
- package/src/lib/types/canvas.ts +71 -0
- package/src/lib/utils/storage.ts +326 -0
- package/src/lib/utils/yaml-loader.ts +52 -0
- package/src/routes/+layout.svelte +5 -0
- package/src/routes/+layout.ts +5 -0
- package/src/routes/+page.svelte +32 -0
- package/src-tauri/Cargo.lock +5735 -0
- package/src-tauri/Cargo.toml +38 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +10 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/agents/agent1.rs +66 -0
- package/src-tauri/src/agents/agent2.rs +80 -0
- package/src-tauri/src/agents/agent3.rs +73 -0
- package/src-tauri/src/agents/agent4.rs +66 -0
- package/src-tauri/src/agents/agent5.rs +68 -0
- package/src-tauri/src/agents/agent6.rs +75 -0
- package/src-tauri/src/agents/base.rs +52 -0
- package/src-tauri/src/agents/mod.rs +17 -0
- package/src-tauri/src/core/coordination.rs +117 -0
- package/src-tauri/src/core/mod.rs +12 -0
- package/src-tauri/src/core/ownership.rs +61 -0
- package/src-tauri/src/core/types.rs +132 -0
- package/src-tauri/src/execution/mod.rs +5 -0
- package/src-tauri/src/execution/runner.rs +143 -0
- package/src-tauri/src/lib.rs +161 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/src/memory/api.rs +422 -0
- package/src-tauri/src/memory/client.rs +156 -0
- package/src-tauri/src/memory/encryption.rs +79 -0
- package/src-tauri/src/memory/migration.rs +110 -0
- package/src-tauri/src/memory/mod.rs +28 -0
- package/src-tauri/src/memory/schema.rs +275 -0
- package/src-tauri/src/memory/tests.rs +192 -0
- package/src-tauri/src/orchestrator/coordinator.rs +232 -0
- package/src-tauri/src/orchestrator/mod.rs +13 -0
- package/src-tauri/src/orchestrator/planner.rs +304 -0
- package/src-tauri/tauri.conf.json +35 -0
- package/static/examples/date-time-example.yaml +147 -0
- package/static/examples/hello-world.yaml +74 -0
- package/static/examples/transform-example.yaml +157 -0
- package/static/favicon.png +0 -0
- package/static/svelte.svg +1 -0
- package/static/tauri.svg +6 -0
- package/static/vite.svg +1 -0
- package/svelte.config.js +18 -0
- package/tsconfig.json +19 -0
- package/vite.config.js +45 -0
- package/vitest.config.ts +21 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Headless CLI for RuneBook
|
|
3
|
+
// SSH-friendly interface without GUI
|
|
4
|
+
|
|
5
|
+
import { createAgent, defaultAgentConfig } from '../lib/agent/index';
|
|
6
|
+
import { formatSuggestionsForCLI, formatSuggestionCompact } from '../lib/agent/suggestions';
|
|
7
|
+
import { getAgentStatus, formatStatus } from '../lib/agent/status';
|
|
8
|
+
import type { AgentConfig } from '../lib/types/agent';
|
|
9
|
+
import { createObserver, type ObserverConfig } from '../lib/core';
|
|
10
|
+
import { getAnalysisService } from '../lib/agent/analysis-service';
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
|
|
15
|
+
const CONFIG_FILE = join(homedir(), '.runebook', 'agent-config.json');
|
|
16
|
+
const OBSERVER_CONFIG_FILE = join(homedir(), '.runebook', 'observer-config.json');
|
|
17
|
+
|
|
18
|
+
interface CLIOptions {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
storagePath?: string;
|
|
21
|
+
maxEvents?: number;
|
|
22
|
+
retentionDays?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load agent configuration from file or use defaults
|
|
27
|
+
*/
|
|
28
|
+
function loadConfig(): AgentConfig {
|
|
29
|
+
if (existsSync(CONFIG_FILE)) {
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
32
|
+
return { ...defaultAgentConfig, ...JSON.parse(content) };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Failed to load config, using defaults:', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { ...defaultAgentConfig };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Save agent configuration to file
|
|
42
|
+
*/
|
|
43
|
+
function saveConfig(config: AgentConfig): void {
|
|
44
|
+
const configDir = join(homedir(), '.runebook');
|
|
45
|
+
if (!existsSync(configDir)) {
|
|
46
|
+
mkdirSync(configDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize agent from CLI
|
|
53
|
+
*/
|
|
54
|
+
function initAgentFromCLI(options: CLIOptions = {}): void {
|
|
55
|
+
const config = loadConfig();
|
|
56
|
+
const updatedConfig: AgentConfig = {
|
|
57
|
+
...config,
|
|
58
|
+
enabled: options.enabled !== undefined ? options.enabled : config.enabled,
|
|
59
|
+
storagePath: options.storagePath || config.storagePath,
|
|
60
|
+
maxEvents: options.maxEvents || config.maxEvents,
|
|
61
|
+
retentionDays: options.retentionDays || config.retentionDays,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
saveConfig(updatedConfig);
|
|
65
|
+
console.log('Agent configuration updated.');
|
|
66
|
+
console.log('Config:', JSON.stringify(updatedConfig, null, 2));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Show agent status
|
|
71
|
+
*/
|
|
72
|
+
async function showStatus(): Promise<void> {
|
|
73
|
+
const config = loadConfig();
|
|
74
|
+
const agent = createAgent(config);
|
|
75
|
+
|
|
76
|
+
console.log('\n=== Ambient Agent Status ===\n');
|
|
77
|
+
console.log(`Enabled: ${config.enabled ? 'Yes' : 'No'}`);
|
|
78
|
+
console.log(`Capture Events: ${config.captureEvents ? 'Yes' : 'No'}`);
|
|
79
|
+
console.log(`Analyze Patterns: ${config.analyzePatterns ? 'Yes' : 'No'}`);
|
|
80
|
+
console.log(`Suggest Improvements: ${config.suggestImprovements ? 'Yes' : 'No'}`);
|
|
81
|
+
console.log(`Storage Path: ${config.storagePath || 'Memory (in-memory)'}`);
|
|
82
|
+
console.log(`Max Events: ${config.maxEvents || 'Unlimited'}`);
|
|
83
|
+
console.log(`Retention Days: ${config.retentionDays || 'Unlimited'}`);
|
|
84
|
+
|
|
85
|
+
if (config.enabled) {
|
|
86
|
+
const stats = await agent.getStats();
|
|
87
|
+
console.log('\n=== Statistics ===');
|
|
88
|
+
console.log(`Total Events: ${stats.totalEvents}`);
|
|
89
|
+
console.log(`Unique Commands: ${stats.uniqueCommands}`);
|
|
90
|
+
console.log(`Average Success Rate: ${(stats.avgSuccessRate * 100).toFixed(1)}%`);
|
|
91
|
+
console.log(`Total Duration: ${(stats.totalDuration / 1000).toFixed(1)}s`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
agent.stop();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Show suggestions
|
|
99
|
+
*/
|
|
100
|
+
async function showSuggestions(priority?: 'low' | 'medium' | 'high'): Promise<void> {
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
|
|
103
|
+
if (!config.enabled) {
|
|
104
|
+
console.log('Agent is not enabled. Run: runebook agent enable');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const agent = createAgent(config);
|
|
109
|
+
const suggestions = agent.getSuggestionsCLI(priority);
|
|
110
|
+
console.log(suggestions);
|
|
111
|
+
|
|
112
|
+
// Also analyze patterns for new suggestions
|
|
113
|
+
await agent.analyzeAllPatterns();
|
|
114
|
+
const newSuggestions = agent.getSuggestionsCLI(priority);
|
|
115
|
+
if (newSuggestions !== 'No suggestions available.\n') {
|
|
116
|
+
console.log('\n=== New Pattern-Based Suggestions ===');
|
|
117
|
+
console.log(newSuggestions);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
agent.stop();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Show agent status
|
|
125
|
+
*/
|
|
126
|
+
function showSuggestStatus(): void {
|
|
127
|
+
const status = getAgentStatus();
|
|
128
|
+
const statusText = formatStatus(status);
|
|
129
|
+
|
|
130
|
+
console.log(`\n=== Agent Status ===\n`);
|
|
131
|
+
console.log(`Status: ${statusText}`);
|
|
132
|
+
console.log(`Suggestions: ${status.suggestionCount}`);
|
|
133
|
+
console.log(`High Priority: ${status.highPriorityCount}`);
|
|
134
|
+
|
|
135
|
+
if (status.lastCommand) {
|
|
136
|
+
const date = new Date(status.lastCommandTimestamp || 0).toLocaleString();
|
|
137
|
+
console.log(`Last Command: ${status.lastCommand} (${date})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Show top suggestion
|
|
145
|
+
*/
|
|
146
|
+
async function showTopSuggestion(): Promise<void> {
|
|
147
|
+
const config = loadConfig();
|
|
148
|
+
|
|
149
|
+
if (!config.enabled) {
|
|
150
|
+
console.log('Agent is not enabled. Run: runebook agent enable');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const agent = createAgent(config);
|
|
155
|
+
const topSuggestions = agent.getTopSuggestion(1);
|
|
156
|
+
|
|
157
|
+
if (topSuggestions.length === 0) {
|
|
158
|
+
console.log('No suggestions available.');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const suggestion = topSuggestions[0];
|
|
163
|
+
console.log(formatSuggestionCompact(suggestion));
|
|
164
|
+
console.log(`\n${suggestion.description}`);
|
|
165
|
+
if (suggestion.command) {
|
|
166
|
+
const args = suggestion.args ? suggestion.args.join(' ') : '';
|
|
167
|
+
console.log(`\nCommand: ${suggestion.command} ${args}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
agent.stop();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Show suggestions for last command
|
|
175
|
+
*/
|
|
176
|
+
async function showLastCommandSuggestions(): Promise<void> {
|
|
177
|
+
const config = loadConfig();
|
|
178
|
+
|
|
179
|
+
if (!config.enabled) {
|
|
180
|
+
console.log('Agent is not enabled. Run: runebook agent enable');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const agent = createAgent(config);
|
|
185
|
+
const suggestions = agent.getSuggestionsForLastCommand();
|
|
186
|
+
const status = getAgentStatus();
|
|
187
|
+
|
|
188
|
+
if (!status.lastCommand) {
|
|
189
|
+
console.log('No command has been executed yet.');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log(`\n=== Suggestions for "${status.lastCommand}" ===\n`);
|
|
194
|
+
|
|
195
|
+
if (suggestions.length === 0) {
|
|
196
|
+
console.log('No suggestions for this command.');
|
|
197
|
+
} else {
|
|
198
|
+
console.log(formatSuggestionsForCLI(suggestions));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
agent.stop();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Show recent events
|
|
206
|
+
*/
|
|
207
|
+
async function showEvents(limit: number = 10): Promise<void> {
|
|
208
|
+
const config = loadConfig();
|
|
209
|
+
|
|
210
|
+
if (!config.enabled) {
|
|
211
|
+
console.log('Agent is not enabled. Run: runebook agent enable');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const agent = createAgent(config);
|
|
216
|
+
const events = await agent.getRecentEvents(limit);
|
|
217
|
+
|
|
218
|
+
console.log(`\n=== Recent Events (${events.length}) ===\n`);
|
|
219
|
+
|
|
220
|
+
for (const event of events) {
|
|
221
|
+
const date = new Date(event.timestamp).toLocaleString();
|
|
222
|
+
const status = event.success ? '✓' : '✗';
|
|
223
|
+
const duration = event.duration ? `${(event.duration / 1000).toFixed(2)}s` : 'N/A';
|
|
224
|
+
const args = event.args.length > 0 ? ` ${event.args.join(' ')}` : '';
|
|
225
|
+
|
|
226
|
+
console.log(`${status} [${date}] ${event.command}${args}`);
|
|
227
|
+
console.log(` Duration: ${duration}, Exit Code: ${event.exitCode || 'N/A'}`);
|
|
228
|
+
if (event.cwd) {
|
|
229
|
+
console.log(` CWD: ${event.cwd}`);
|
|
230
|
+
}
|
|
231
|
+
console.log();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
agent.stop();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clear old events
|
|
239
|
+
*/
|
|
240
|
+
async function clearEvents(days: number = 30): Promise<void> {
|
|
241
|
+
const config = loadConfig();
|
|
242
|
+
|
|
243
|
+
if (!config.enabled) {
|
|
244
|
+
console.log('Agent is not enabled.');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const agent = createAgent(config);
|
|
249
|
+
await agent.clearOldEvents(days);
|
|
250
|
+
console.log(`Cleared events older than ${days} days.`);
|
|
251
|
+
|
|
252
|
+
agent.stop();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Load observer configuration
|
|
257
|
+
*/
|
|
258
|
+
function loadObserverConfig(): ObserverConfig {
|
|
259
|
+
if (existsSync(OBSERVER_CONFIG_FILE)) {
|
|
260
|
+
try {
|
|
261
|
+
const content = readFileSync(OBSERVER_CONFIG_FILE, 'utf-8');
|
|
262
|
+
return JSON.parse(content);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error('Failed to load observer config, using defaults:', error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
enabled: false,
|
|
269
|
+
redactSecrets: true,
|
|
270
|
+
usePluresDB: false,
|
|
271
|
+
chunkSize: 4096,
|
|
272
|
+
maxEvents: 10000,
|
|
273
|
+
retentionDays: 30,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Save observer configuration
|
|
279
|
+
*/
|
|
280
|
+
function saveObserverConfig(config: ObserverConfig): void {
|
|
281
|
+
const configDir = join(homedir(), '.runebook');
|
|
282
|
+
if (!existsSync(configDir)) {
|
|
283
|
+
mkdirSync(configDir, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
writeFileSync(OBSERVER_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Tail events (stream events as they come in)
|
|
290
|
+
*/
|
|
291
|
+
async function tailEvents(follow: boolean = true): Promise<void> {
|
|
292
|
+
const config = loadObserverConfig();
|
|
293
|
+
|
|
294
|
+
if (!config.enabled) {
|
|
295
|
+
console.log('Observer is not enabled. Run: runebook observer enable');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const observer = createObserver(config);
|
|
300
|
+
await observer.initialize();
|
|
301
|
+
|
|
302
|
+
console.log('Tailing events (press Ctrl+C to stop)...\n');
|
|
303
|
+
|
|
304
|
+
let lastTimestamp = Date.now();
|
|
305
|
+
|
|
306
|
+
const pollInterval = setInterval(async () => {
|
|
307
|
+
try {
|
|
308
|
+
const events = await observer.getEvents(undefined, lastTimestamp);
|
|
309
|
+
|
|
310
|
+
for (const event of events) {
|
|
311
|
+
const date = new Date(event.timestamp).toISOString();
|
|
312
|
+
const type = event.type.padEnd(15);
|
|
313
|
+
|
|
314
|
+
switch (event.type) {
|
|
315
|
+
case 'command_start':
|
|
316
|
+
console.log(`[${date}] ${type} ${event.command} ${event.args.join(' ')}`);
|
|
317
|
+
console.log(` CWD: ${event.cwd}`);
|
|
318
|
+
break;
|
|
319
|
+
case 'command_end':
|
|
320
|
+
console.log(`[${date}] ${type} commandId: ${event.commandId}, duration: ${event.duration}ms`);
|
|
321
|
+
break;
|
|
322
|
+
case 'stdout_chunk':
|
|
323
|
+
console.log(`[${date}] ${type} [${event.chunkIndex}] ${event.chunk.substring(0, 100)}${event.chunk.length > 100 ? '...' : ''}`);
|
|
324
|
+
break;
|
|
325
|
+
case 'stderr_chunk':
|
|
326
|
+
console.log(`[${date}] ${type} [${event.chunkIndex}] ${event.chunk.substring(0, 100)}${event.chunk.length > 100 ? '...' : ''}`);
|
|
327
|
+
break;
|
|
328
|
+
case 'exit_status':
|
|
329
|
+
console.log(`[${date}] ${type} exitCode: ${event.exitCode}, success: ${event.success}`);
|
|
330
|
+
break;
|
|
331
|
+
default:
|
|
332
|
+
console.log(`[${date}] ${type} ${JSON.stringify(event).substring(0, 100)}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
lastTimestamp = Math.max(lastTimestamp, event.timestamp);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error('Error tailing events:', error);
|
|
339
|
+
}
|
|
340
|
+
}, 1000); // Poll every second
|
|
341
|
+
|
|
342
|
+
// Handle Ctrl+C
|
|
343
|
+
process.on('SIGINT', () => {
|
|
344
|
+
clearInterval(pollInterval);
|
|
345
|
+
observer.stop();
|
|
346
|
+
console.log('\nStopped tailing events.');
|
|
347
|
+
process.exit(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
if (!follow) {
|
|
351
|
+
// One-time fetch
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
clearInterval(pollInterval);
|
|
354
|
+
observer.stop();
|
|
355
|
+
}, 5000);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Show LLM provider status
|
|
361
|
+
*/
|
|
362
|
+
async function showLLMStatus(): Promise<void> {
|
|
363
|
+
const obsConfig = loadObserverConfig();
|
|
364
|
+
|
|
365
|
+
console.log('\n=== LLM/MCP Integration Status ===\n');
|
|
366
|
+
|
|
367
|
+
// Check if llm config exists (extended observer config)
|
|
368
|
+
const llmConfig = (obsConfig as any).llm;
|
|
369
|
+
if (!llmConfig) {
|
|
370
|
+
console.log('Status: Disabled (no configuration)');
|
|
371
|
+
console.log('\nTo enable LLM analysis, add LLM configuration to observer config.');
|
|
372
|
+
console.log('Example:');
|
|
373
|
+
console.log(' {');
|
|
374
|
+
console.log(' "llm": {');
|
|
375
|
+
console.log(' "type": "ollama",');
|
|
376
|
+
console.log(' "enabled": true,');
|
|
377
|
+
console.log(' "ollama": { "model": "llama3.2" }');
|
|
378
|
+
console.log(' }');
|
|
379
|
+
console.log(' }');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log(`Provider Type: ${llmConfig.type}`);
|
|
384
|
+
console.log(`Enabled: ${llmConfig.enabled ? 'Yes' : 'No'}`);
|
|
385
|
+
|
|
386
|
+
if (!llmConfig.enabled) {
|
|
387
|
+
console.log('\nLLM analysis is configured but disabled.');
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check provider availability
|
|
392
|
+
try {
|
|
393
|
+
const { createLLMProvider, isProviderAvailable } = await import('../lib/agent/llm/providers');
|
|
394
|
+
const available = await isProviderAvailable(llmConfig);
|
|
395
|
+
console.log(`Available: ${available ? 'Yes' : 'No'}`);
|
|
396
|
+
|
|
397
|
+
if (available) {
|
|
398
|
+
const provider = createLLMProvider(llmConfig);
|
|
399
|
+
if (provider) {
|
|
400
|
+
console.log(`Provider: ${provider.name}`);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
console.log('\n⚠️ Provider is not available.');
|
|
404
|
+
if (llmConfig.type === 'ollama') {
|
|
405
|
+
console.log('Make sure Ollama is running: ollama serve');
|
|
406
|
+
} else if (llmConfig.type === 'openai') {
|
|
407
|
+
console.log('Make sure OPENAI_API_KEY environment variable is set.');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Show safety settings
|
|
412
|
+
if (llmConfig.safety) {
|
|
413
|
+
console.log('\n=== Safety Settings ===');
|
|
414
|
+
console.log(`Require User Review: ${llmConfig.safety.requireUserReview !== false ? 'Yes' : 'No'}`);
|
|
415
|
+
console.log(`Cache Enabled: ${llmConfig.safety.cacheEnabled ? 'Yes' : 'No'}`);
|
|
416
|
+
if (llmConfig.safety.cacheEnabled) {
|
|
417
|
+
console.log(`Cache TTL: ${llmConfig.safety.cacheTtl || 3600} seconds`);
|
|
418
|
+
}
|
|
419
|
+
if (llmConfig.safety.maxContextLength) {
|
|
420
|
+
console.log(`Max Context Length: ${llmConfig.safety.maxContextLength} tokens`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Show provider-specific config
|
|
425
|
+
console.log('\n=== Provider Configuration ===');
|
|
426
|
+
if (llmConfig.type === 'ollama' && llmConfig.ollama) {
|
|
427
|
+
console.log(`Base URL: ${llmConfig.ollama.baseUrl || 'http://localhost:11434'}`);
|
|
428
|
+
console.log(`Model: ${llmConfig.ollama.model || 'llama3.2'}`);
|
|
429
|
+
} else if (llmConfig.type === 'openai' && llmConfig.openai) {
|
|
430
|
+
const apiKey = llmConfig.openai.apiKey || process.env.OPENAI_API_KEY;
|
|
431
|
+
console.log(`API Key: ${apiKey ? '***' + apiKey.slice(-4) : 'Not set'}`);
|
|
432
|
+
console.log(`Model: ${llmConfig.openai.model || 'gpt-4o-mini'}`);
|
|
433
|
+
console.log(`Base URL: ${llmConfig.openai.baseUrl || 'https://api.openai.com/v1'}`);
|
|
434
|
+
} else if (llmConfig.type === 'mock') {
|
|
435
|
+
console.log('Mock provider (for testing)');
|
|
436
|
+
} else if (llmConfig.type === 'mcp') {
|
|
437
|
+
console.log('MCP provider (not yet implemented)');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
} catch (error: any) {
|
|
441
|
+
console.error('Error checking LLM status:', error.message);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Inspect cognitive memory storage
|
|
449
|
+
*/
|
|
450
|
+
async function inspectMemory(): Promise<void> {
|
|
451
|
+
try {
|
|
452
|
+
const { SQLiteCompatibleAPI } = await import('pluresdb');
|
|
453
|
+
|
|
454
|
+
const db = new SQLiteCompatibleAPI({
|
|
455
|
+
config: {
|
|
456
|
+
port: 34567,
|
|
457
|
+
host: 'localhost',
|
|
458
|
+
dataDir: './pluresdb-data',
|
|
459
|
+
},
|
|
460
|
+
autoStart: true,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
await db.start();
|
|
464
|
+
|
|
465
|
+
// List sessions
|
|
466
|
+
const sessionKeys = await db.list('memory:session:');
|
|
467
|
+
const sessions = [];
|
|
468
|
+
for (const key of sessionKeys) {
|
|
469
|
+
try {
|
|
470
|
+
const session = await db.getValue(key);
|
|
471
|
+
if (session) {
|
|
472
|
+
sessions.push(session);
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
// Skip errors
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// List errors
|
|
480
|
+
const errorKeys = await db.list('memory:error:');
|
|
481
|
+
const errors = [];
|
|
482
|
+
for (const key of errorKeys.slice(0, 10)) {
|
|
483
|
+
try {
|
|
484
|
+
const error = await db.getValue(key);
|
|
485
|
+
if (error) {
|
|
486
|
+
errors.push(error);
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
// Skip errors
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// List suggestions
|
|
494
|
+
const suggestionKeys = await db.list('memory:suggestion:');
|
|
495
|
+
const suggestions = [];
|
|
496
|
+
for (const key of suggestionKeys.slice(0, 10)) {
|
|
497
|
+
try {
|
|
498
|
+
const suggestion = await db.getValue(key);
|
|
499
|
+
if (suggestion && !suggestion.dismissed) {
|
|
500
|
+
suggestions.push(suggestion);
|
|
501
|
+
}
|
|
502
|
+
} catch (error) {
|
|
503
|
+
// Skip errors
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Sort suggestions by rank
|
|
508
|
+
suggestions.sort((a, b) => (b.rank || 0) - (a.rank || 0));
|
|
509
|
+
|
|
510
|
+
console.log('\n=== RuneBook Cognitive Memory ===\n');
|
|
511
|
+
console.log(`Sessions: ${sessions.length}`);
|
|
512
|
+
console.log(`Recent Errors: ${errors.length}`);
|
|
513
|
+
console.log(`Active Suggestions: ${suggestions.length}\n`);
|
|
514
|
+
|
|
515
|
+
if (sessions.length > 0) {
|
|
516
|
+
console.log('=== Recent Sessions ===');
|
|
517
|
+
for (const session of sessions.slice(0, 5)) {
|
|
518
|
+
const started = new Date(session.started_at).toLocaleString();
|
|
519
|
+
console.log(` ${session.id.substring(0, 8)}... - ${session.shell_type} (started: ${started})`);
|
|
520
|
+
}
|
|
521
|
+
console.log();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (errors.length > 0) {
|
|
525
|
+
console.log('=== Recent Errors ===');
|
|
526
|
+
for (const error of errors.slice(0, 5)) {
|
|
527
|
+
console.log(` [${error.severity}] ${error.error_type} - ${error.message.substring(0, 60)}`);
|
|
528
|
+
}
|
|
529
|
+
console.log();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (suggestions.length > 0) {
|
|
533
|
+
console.log('=== Top Suggestions ===');
|
|
534
|
+
for (const suggestion of suggestions.slice(0, 5)) {
|
|
535
|
+
console.log(` [${suggestion.priority}] ${suggestion.title} - ${suggestion.description.substring(0, 60)}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
} catch (error: any) {
|
|
540
|
+
if (error.message && error.message.includes('PluresDB')) {
|
|
541
|
+
console.error('Error: PluresDB server is not available.');
|
|
542
|
+
console.error('Make sure PluresDB is running on localhost:34567');
|
|
543
|
+
console.error('Or use local storage by setting usePluresDB: false in config');
|
|
544
|
+
} else {
|
|
545
|
+
console.error('Error inspecting memory:', error);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Wipe all cognitive memory storage
|
|
552
|
+
*/
|
|
553
|
+
async function wipeMemory(): Promise<void> {
|
|
554
|
+
try {
|
|
555
|
+
const { SQLiteCompatibleAPI } = await import('pluresdb');
|
|
556
|
+
|
|
557
|
+
const db = new SQLiteCompatibleAPI({
|
|
558
|
+
config: {
|
|
559
|
+
port: 34567,
|
|
560
|
+
host: 'localhost',
|
|
561
|
+
dataDir: './pluresdb-data',
|
|
562
|
+
},
|
|
563
|
+
autoStart: true,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await db.start();
|
|
567
|
+
|
|
568
|
+
// Wipe all memory prefixes (matching Rust API implementation)
|
|
569
|
+
const prefixes = [
|
|
570
|
+
'memory:session:',
|
|
571
|
+
'memory:command:',
|
|
572
|
+
'memory:output:',
|
|
573
|
+
'memory:error:',
|
|
574
|
+
'memory:insight:',
|
|
575
|
+
'memory:suggestion:',
|
|
576
|
+
'memory:provenance:',
|
|
577
|
+
'memory:event:',
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
let totalDeleted = 0;
|
|
581
|
+
|
|
582
|
+
for (const prefix of prefixes) {
|
|
583
|
+
const keys = await db.list(prefix);
|
|
584
|
+
for (const key of keys) {
|
|
585
|
+
try {
|
|
586
|
+
await db.delete(key);
|
|
587
|
+
totalDeleted++;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error(`Failed to delete ${key}:`, error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.log(`\n✅ Wiped all memory data (${totalDeleted} items deleted)\n`);
|
|
595
|
+
console.log('Warning: This permanently deleted all stored data.');
|
|
596
|
+
|
|
597
|
+
} catch (error: any) {
|
|
598
|
+
if (error.message && error.message.includes('PluresDB')) {
|
|
599
|
+
console.error('Error: PluresDB server is not available.');
|
|
600
|
+
console.error('Make sure PluresDB is running on localhost:34567');
|
|
601
|
+
} else {
|
|
602
|
+
console.error('Error wiping memory:', error);
|
|
603
|
+
}
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Show observer events
|
|
610
|
+
*/
|
|
611
|
+
async function showObserverEvents(limit: number = 10): Promise<void> {
|
|
612
|
+
const config = loadObserverConfig();
|
|
613
|
+
|
|
614
|
+
if (!config.enabled) {
|
|
615
|
+
console.log('Observer is not enabled. Run: runebook observer enable');
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const observer = createObserver(config);
|
|
620
|
+
await observer.initialize();
|
|
621
|
+
const events = await observer.getEvents(undefined, undefined, limit);
|
|
622
|
+
|
|
623
|
+
console.log(`\n=== Observer Events (${events.length}) ===\n`);
|
|
624
|
+
|
|
625
|
+
for (const event of events) {
|
|
626
|
+
const date = new Date(event.timestamp).toISOString();
|
|
627
|
+
console.log(`[${date}] ${event.type}`);
|
|
628
|
+
console.log(` Session: ${event.sessionId}`);
|
|
629
|
+
console.log(` Shell: ${event.shellType}`);
|
|
630
|
+
if (event.type === 'command_start') {
|
|
631
|
+
console.log(` Command: ${event.command} ${event.args.join(' ')}`);
|
|
632
|
+
console.log(` CWD: ${event.cwd}`);
|
|
633
|
+
}
|
|
634
|
+
console.log();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
observer.stop();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Show analysis results for last failure
|
|
642
|
+
*/
|
|
643
|
+
async function showLastAnalysis(): Promise<void> {
|
|
644
|
+
const obsConfig = loadObserverConfig();
|
|
645
|
+
|
|
646
|
+
if (!obsConfig.enabled) {
|
|
647
|
+
console.log('Observer is not enabled. Run: runebook observer enable');
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const observer = createObserver(obsConfig);
|
|
652
|
+
await observer.initialize();
|
|
653
|
+
|
|
654
|
+
// Get the store from observer (it's created during initialize)
|
|
655
|
+
// We need to access it through the observer's internal state
|
|
656
|
+
// For now, we'll create a new store with the same config
|
|
657
|
+
const { createEventStore } = await import('../lib/core/storage');
|
|
658
|
+
const store = createEventStore(obsConfig);
|
|
659
|
+
|
|
660
|
+
const analysisService = getAnalysisService();
|
|
661
|
+
analysisService.initialize(store, obsConfig);
|
|
662
|
+
analysisService.setEnabled(true);
|
|
663
|
+
|
|
664
|
+
// Get last exit_status event that failed
|
|
665
|
+
const events = await observer.getEvents('exit_status', undefined, 50);
|
|
666
|
+
const failures = events.filter(e => e.type === 'exit_status' && !e.success);
|
|
667
|
+
|
|
668
|
+
if (failures.length === 0) {
|
|
669
|
+
console.log('No recent failures found.');
|
|
670
|
+
observer.stop();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Process the most recent failure
|
|
675
|
+
const lastFailure = failures[0];
|
|
676
|
+
const jobId = await analysisService.processExitStatus(lastFailure);
|
|
677
|
+
|
|
678
|
+
if (!jobId) {
|
|
679
|
+
console.log('Failed to create analysis job.');
|
|
680
|
+
observer.stop();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Wait a bit for analysis to complete (non-blocking, but we'll poll)
|
|
685
|
+
let job = analysisService.getJob(jobId);
|
|
686
|
+
let attempts = 0;
|
|
687
|
+
while (job && job.status === 'running' && attempts < 30) {
|
|
688
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
689
|
+
job = analysisService.getJob(jobId);
|
|
690
|
+
attempts++;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
job = analysisService.getJob(jobId);
|
|
694
|
+
|
|
695
|
+
if (!job) {
|
|
696
|
+
console.log('Analysis job not found.');
|
|
697
|
+
observer.stop();
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log('\n=== Analysis Results ===\n');
|
|
702
|
+
console.log(`Command: ${job.command} ${job.args.join(' ')}`);
|
|
703
|
+
console.log(`Exit Code: ${job.exitCode}`);
|
|
704
|
+
console.log(`Status: ${job.status}`);
|
|
705
|
+
console.log(`CWD: ${job.cwd}`);
|
|
706
|
+
|
|
707
|
+
if (job.stderr) {
|
|
708
|
+
console.log(`\nStderr:\n${job.stderr.substring(0, 500)}${job.stderr.length > 500 ? '...' : ''}`);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (job.suggestions.length === 0) {
|
|
712
|
+
console.log('\nNo suggestions generated.');
|
|
713
|
+
} else {
|
|
714
|
+
console.log(`\n=== Suggestions (${job.suggestions.length}) ===\n`);
|
|
715
|
+
|
|
716
|
+
for (const suggestion of job.suggestions) {
|
|
717
|
+
console.log(`[${suggestion.provenance.analyzer}] ${suggestion.title} (confidence: ${(suggestion.confidence * 100).toFixed(0)}%)`);
|
|
718
|
+
console.log(` ${suggestion.description}`);
|
|
719
|
+
if (suggestion.actionableSnippet) {
|
|
720
|
+
console.log(`\n ${suggestion.actionableSnippet.split('\n').join('\n ')}`);
|
|
721
|
+
}
|
|
722
|
+
console.log();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
observer.stop();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Main CLI handler
|
|
731
|
+
*/
|
|
732
|
+
function main() {
|
|
733
|
+
const args = process.argv.slice(2);
|
|
734
|
+
const command = args[0];
|
|
735
|
+
const subcommand = args[1];
|
|
736
|
+
|
|
737
|
+
if (!command) {
|
|
738
|
+
console.log(`
|
|
739
|
+
RuneBook CLI
|
|
740
|
+
|
|
741
|
+
Usage:
|
|
742
|
+
runebook <command> [subcommand] [options]
|
|
743
|
+
|
|
744
|
+
Commands:
|
|
745
|
+
agent <command> Agent commands (enable, disable, status, suggestions, events, clear, config)
|
|
746
|
+
observer <command> Observer commands (enable, disable, status, events, tail)
|
|
747
|
+
analyze <command> Analysis commands (last)
|
|
748
|
+
memory <command> Memory commands (inspect)
|
|
749
|
+
llm <command> LLM/MCP commands (status)
|
|
750
|
+
|
|
751
|
+
Examples:
|
|
752
|
+
runebook agent enable
|
|
753
|
+
runebook agent status
|
|
754
|
+
runebook suggest status
|
|
755
|
+
runebook suggest top
|
|
756
|
+
runebook suggest last
|
|
757
|
+
runebook observer enable
|
|
758
|
+
runebook observer events tail
|
|
759
|
+
runebook observer events 20
|
|
760
|
+
runebook memory inspect
|
|
761
|
+
`);
|
|
762
|
+
process.exit(0);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
(async () => {
|
|
766
|
+
try {
|
|
767
|
+
if (command === 'agent') {
|
|
768
|
+
// Agent commands
|
|
769
|
+
if (!subcommand) {
|
|
770
|
+
console.log(`
|
|
771
|
+
RuneBook Agent CLI
|
|
772
|
+
|
|
773
|
+
Usage:
|
|
774
|
+
runebook agent <command> [options]
|
|
775
|
+
|
|
776
|
+
Commands:
|
|
777
|
+
enable Enable the agent
|
|
778
|
+
disable Disable the agent
|
|
779
|
+
status Show agent status and statistics
|
|
780
|
+
suggestions Show current suggestions
|
|
781
|
+
events [limit] Show recent events (default: 10)
|
|
782
|
+
clear [days] Clear events older than N days (default: 30)
|
|
783
|
+
config <key> <val> Set configuration option
|
|
784
|
+
`);
|
|
785
|
+
process.exit(0);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
switch (subcommand) {
|
|
789
|
+
case 'enable':
|
|
790
|
+
initAgentFromCLI({ enabled: true });
|
|
791
|
+
console.log('Agent enabled.');
|
|
792
|
+
break;
|
|
793
|
+
|
|
794
|
+
case 'disable':
|
|
795
|
+
initAgentFromCLI({ enabled: false });
|
|
796
|
+
console.log('Agent disabled.');
|
|
797
|
+
break;
|
|
798
|
+
|
|
799
|
+
case 'status':
|
|
800
|
+
await showStatus();
|
|
801
|
+
break;
|
|
802
|
+
|
|
803
|
+
case 'suggestions':
|
|
804
|
+
const priority = args[2] as 'low' | 'medium' | 'high' | undefined;
|
|
805
|
+
await showSuggestions(priority);
|
|
806
|
+
break;
|
|
807
|
+
|
|
808
|
+
case 'events':
|
|
809
|
+
const limit = args[2] ? parseInt(args[2], 10) : 10;
|
|
810
|
+
await showEvents(limit);
|
|
811
|
+
break;
|
|
812
|
+
|
|
813
|
+
case 'clear':
|
|
814
|
+
const days = args[2] ? parseInt(args[2], 10) : 30;
|
|
815
|
+
await clearEvents(days);
|
|
816
|
+
break;
|
|
817
|
+
|
|
818
|
+
case 'config':
|
|
819
|
+
const key = args[2];
|
|
820
|
+
const value = args[3];
|
|
821
|
+
if (!key || !value) {
|
|
822
|
+
console.error('Usage: runebook agent config <key> <value>');
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
const config = loadConfig();
|
|
826
|
+
(config as any)[key] = isNaN(Number(value)) ? value : Number(value);
|
|
827
|
+
saveConfig(config);
|
|
828
|
+
console.log(`Set ${key} = ${value}`);
|
|
829
|
+
break;
|
|
830
|
+
|
|
831
|
+
default:
|
|
832
|
+
console.error(`Unknown agent command: ${subcommand}`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
} else if (command === 'observer') {
|
|
836
|
+
// Observer commands
|
|
837
|
+
if (!subcommand) {
|
|
838
|
+
console.log(`
|
|
839
|
+
RuneBook Observer CLI
|
|
840
|
+
|
|
841
|
+
Usage:
|
|
842
|
+
runebook observer <command> [options]
|
|
843
|
+
|
|
844
|
+
Commands:
|
|
845
|
+
enable Enable the observer
|
|
846
|
+
disable Disable the observer
|
|
847
|
+
status Show observer status
|
|
848
|
+
events [limit] Show recent events (default: 10)
|
|
849
|
+
events tail Tail events in real-time
|
|
850
|
+
`);
|
|
851
|
+
process.exit(0);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
switch (subcommand) {
|
|
855
|
+
case 'enable':
|
|
856
|
+
const obsConfig = loadObserverConfig();
|
|
857
|
+
obsConfig.enabled = true;
|
|
858
|
+
saveObserverConfig(obsConfig);
|
|
859
|
+
console.log('Observer enabled.');
|
|
860
|
+
break;
|
|
861
|
+
|
|
862
|
+
case 'disable':
|
|
863
|
+
const obsConfig2 = loadObserverConfig();
|
|
864
|
+
obsConfig2.enabled = false;
|
|
865
|
+
saveObserverConfig(obsConfig2);
|
|
866
|
+
console.log('Observer disabled.');
|
|
867
|
+
break;
|
|
868
|
+
|
|
869
|
+
case 'status':
|
|
870
|
+
const obsConfig3 = loadObserverConfig();
|
|
871
|
+
const observer = createObserver(obsConfig3);
|
|
872
|
+
await observer.initialize();
|
|
873
|
+
const stats = await observer.getStats();
|
|
874
|
+
|
|
875
|
+
console.log('\n=== Terminal Observer Status ===\n');
|
|
876
|
+
console.log(`Enabled: ${obsConfig3.enabled ? 'Yes' : 'No'}`);
|
|
877
|
+
console.log(`Shell Type: ${obsConfig3.shellType || 'auto-detect'}`);
|
|
878
|
+
console.log(`Redact Secrets: ${obsConfig3.redactSecrets ? 'Yes' : 'No'}`);
|
|
879
|
+
console.log(`Storage: ${obsConfig3.usePluresDB ? 'PluresDB' : 'Local'}`);
|
|
880
|
+
console.log(`Storage Path: ${obsConfig3.storagePath || 'In-memory'}`);
|
|
881
|
+
console.log(`Max Events: ${obsConfig3.maxEvents || 'Unlimited'}`);
|
|
882
|
+
console.log(`Retention Days: ${obsConfig3.retentionDays || 'Unlimited'}`);
|
|
883
|
+
console.log('\n=== Statistics ===');
|
|
884
|
+
console.log(`Total Events: ${stats.totalEvents}`);
|
|
885
|
+
console.log(`Sessions: ${stats.sessions}`);
|
|
886
|
+
console.log(`Events by Type:`);
|
|
887
|
+
for (const [type, count] of Object.entries(stats.eventsByType)) {
|
|
888
|
+
console.log(` ${type}: ${count}`);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
observer.stop();
|
|
892
|
+
break;
|
|
893
|
+
|
|
894
|
+
case 'events':
|
|
895
|
+
if (args[2] === 'tail') {
|
|
896
|
+
await tailEvents(true);
|
|
897
|
+
} else {
|
|
898
|
+
const limit = args[2] ? parseInt(args[2], 10) : 10;
|
|
899
|
+
await showObserverEvents(limit);
|
|
900
|
+
}
|
|
901
|
+
break;
|
|
902
|
+
|
|
903
|
+
default:
|
|
904
|
+
console.error(`Unknown observer command: ${subcommand}`);
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
} else if (command === 'suggest') {
|
|
908
|
+
// Suggestion commands
|
|
909
|
+
if (!subcommand) {
|
|
910
|
+
console.log(`
|
|
911
|
+
RuneBook Suggest CLI
|
|
912
|
+
|
|
913
|
+
Usage:
|
|
914
|
+
runebook suggest <command>
|
|
915
|
+
|
|
916
|
+
Commands:
|
|
917
|
+
status Show current agent status (idle/analyzing/issues found)
|
|
918
|
+
top Show top suggestion on demand
|
|
919
|
+
last Show suggestions for last command
|
|
920
|
+
|
|
921
|
+
Examples:
|
|
922
|
+
runebook suggest status
|
|
923
|
+
runebook suggest top
|
|
924
|
+
runebook suggest last
|
|
925
|
+
`);
|
|
926
|
+
process.exit(0);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
switch (subcommand) {
|
|
930
|
+
case 'status':
|
|
931
|
+
showSuggestStatus();
|
|
932
|
+
break;
|
|
933
|
+
|
|
934
|
+
case 'top':
|
|
935
|
+
await showTopSuggestion();
|
|
936
|
+
break;
|
|
937
|
+
|
|
938
|
+
case 'last':
|
|
939
|
+
await showLastCommandSuggestions();
|
|
940
|
+
break;
|
|
941
|
+
|
|
942
|
+
default:
|
|
943
|
+
console.error(`Unknown suggest command: ${subcommand}`);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
} else if (command === 'analyze') {
|
|
947
|
+
// Analysis commands
|
|
948
|
+
if (!subcommand) {
|
|
949
|
+
console.log(`
|
|
950
|
+
RuneBook Analysis CLI
|
|
951
|
+
|
|
952
|
+
Usage:
|
|
953
|
+
runebook analyze <command> [options]
|
|
954
|
+
|
|
955
|
+
Commands:
|
|
956
|
+
last Analyze the last command failure
|
|
957
|
+
`);
|
|
958
|
+
process.exit(0);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
switch (subcommand) {
|
|
962
|
+
case 'last':
|
|
963
|
+
await showLastAnalysis();
|
|
964
|
+
break;
|
|
965
|
+
|
|
966
|
+
default:
|
|
967
|
+
console.error(`Unknown analyze command: ${subcommand}`);
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
} else if (command === 'memory') {
|
|
971
|
+
// Memory commands
|
|
972
|
+
if (!subcommand) {
|
|
973
|
+
console.log(`
|
|
974
|
+
RuneBook Memory CLI
|
|
975
|
+
|
|
976
|
+
Usage:
|
|
977
|
+
runebook memory <command> [options]
|
|
978
|
+
|
|
979
|
+
Commands:
|
|
980
|
+
inspect Inspect cognitive memory storage
|
|
981
|
+
wipe Wipe all cognitive memory data (permanent deletion)
|
|
982
|
+
`);
|
|
983
|
+
process.exit(0);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
switch (subcommand) {
|
|
987
|
+
case 'inspect':
|
|
988
|
+
await inspectMemory();
|
|
989
|
+
break;
|
|
990
|
+
|
|
991
|
+
case 'wipe':
|
|
992
|
+
// Confirm before wiping
|
|
993
|
+
console.log('⚠️ WARNING: This will permanently delete all memory data.');
|
|
994
|
+
console.log('This includes sessions, commands, outputs, errors, insights, suggestions, and provenance.');
|
|
995
|
+
console.log('');
|
|
996
|
+
console.log('To proceed, run: runebook memory wipe --confirm');
|
|
997
|
+
if (args[2] === '--confirm') {
|
|
998
|
+
await wipeMemory();
|
|
999
|
+
} else {
|
|
1000
|
+
console.log('\nUse --confirm flag to proceed with deletion.');
|
|
1001
|
+
}
|
|
1002
|
+
break;
|
|
1003
|
+
|
|
1004
|
+
default:
|
|
1005
|
+
console.error(`Unknown memory command: ${subcommand}`);
|
|
1006
|
+
process.exit(1);
|
|
1007
|
+
}
|
|
1008
|
+
} else if (command === 'llm') {
|
|
1009
|
+
// LLM commands
|
|
1010
|
+
if (!subcommand) {
|
|
1011
|
+
console.log(`
|
|
1012
|
+
RuneBook LLM CLI
|
|
1013
|
+
|
|
1014
|
+
Usage:
|
|
1015
|
+
runebook llm <command> [options]
|
|
1016
|
+
|
|
1017
|
+
Commands:
|
|
1018
|
+
status Show LLM provider status and configuration
|
|
1019
|
+
`);
|
|
1020
|
+
process.exit(0);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
switch (subcommand) {
|
|
1024
|
+
case 'status':
|
|
1025
|
+
await showLLMStatus();
|
|
1026
|
+
break;
|
|
1027
|
+
|
|
1028
|
+
default:
|
|
1029
|
+
console.error(`Unknown llm command: ${subcommand}`);
|
|
1030
|
+
process.exit(1);
|
|
1031
|
+
}
|
|
1032
|
+
} else {
|
|
1033
|
+
console.error(`Unknown command: ${command}`);
|
|
1034
|
+
console.log('Use "runebook agent", "runebook observer", "runebook suggest", "runebook analyze", "runebook memory", or "runebook llm"');
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error('Error:', error);
|
|
1039
|
+
process.exit(1);
|
|
1040
|
+
}
|
|
1041
|
+
})();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Run if called directly
|
|
1045
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1046
|
+
main();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export { main, loadConfig, saveConfig };
|
|
1050
|
+
|