bashbros 0.1.3 → 0.1.4

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 (66) hide show
  1. package/README.md +727 -265
  2. package/dist/adapters-JAZGGNVP.js +9 -0
  3. package/dist/chunk-4XZ64P4V.js +47 -0
  4. package/dist/chunk-4XZ64P4V.js.map +1 -0
  5. package/dist/{chunk-2RPTM6EQ.js → chunk-7OEWYFN3.js} +745 -629
  6. package/dist/chunk-7OEWYFN3.js.map +1 -0
  7. package/dist/{chunk-WPJJZLT6.js → chunk-CG6VEHJM.js} +3 -2
  8. package/dist/chunk-CG6VEHJM.js.map +1 -0
  9. package/dist/{chunk-DLP2O6PN.js → chunk-EMLEJVJZ.js} +102 -1
  10. package/dist/chunk-EMLEJVJZ.js.map +1 -0
  11. package/dist/chunk-IUUBCPMV.js +166 -0
  12. package/dist/chunk-IUUBCPMV.js.map +1 -0
  13. package/dist/chunk-J6ONXY6N.js +146 -0
  14. package/dist/chunk-J6ONXY6N.js.map +1 -0
  15. package/dist/{chunk-EYO44OMN.js → chunk-KYDMPE4N.js} +60 -17
  16. package/dist/chunk-KYDMPE4N.js.map +1 -0
  17. package/dist/chunk-LJE4EPIU.js +56 -0
  18. package/dist/chunk-LJE4EPIU.js.map +1 -0
  19. package/dist/chunk-LZYW7XQO.js +339 -0
  20. package/dist/chunk-LZYW7XQO.js.map +1 -0
  21. package/dist/{chunk-JYWQT2B4.js → chunk-RDNSS3ME.js} +489 -14
  22. package/dist/chunk-RDNSS3ME.js.map +1 -0
  23. package/dist/{chunk-A535VV7N.js → chunk-RTZ4QWG2.js} +5 -4
  24. package/dist/chunk-RTZ4QWG2.js.map +1 -0
  25. package/dist/chunk-SDN6TAGD.js +157 -0
  26. package/dist/chunk-SDN6TAGD.js.map +1 -0
  27. package/dist/chunk-T5ONCUHZ.js +198 -0
  28. package/dist/chunk-T5ONCUHZ.js.map +1 -0
  29. package/dist/cli.js +1069 -88
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{config-43SK6SFI.js → config-I5NCK3RJ.js} +2 -2
  32. package/dist/copilot-cli-5WJWK5YT.js +9 -0
  33. package/dist/{db-SWJUUSFX.js → db-ETWTBXAE.js} +2 -2
  34. package/dist/db-checks-2YOVECD4.js +133 -0
  35. package/dist/db-checks-2YOVECD4.js.map +1 -0
  36. package/dist/{display-HFIFXOOL.js → display-UH7KEHOW.js} +3 -3
  37. package/dist/gemini-cli-3563EELZ.js +9 -0
  38. package/dist/gemini-cli-3563EELZ.js.map +1 -0
  39. package/dist/index.d.ts +176 -72
  40. package/dist/index.js +119 -398
  41. package/dist/index.js.map +1 -1
  42. package/dist/{ollama-HY35OHW4.js → ollama-5JVKNFOV.js} +2 -2
  43. package/dist/ollama-5JVKNFOV.js.map +1 -0
  44. package/dist/opencode-DRCY275R.js +9 -0
  45. package/dist/opencode-DRCY275R.js.map +1 -0
  46. package/dist/profiles-7CLN6TAT.js +9 -0
  47. package/dist/profiles-7CLN6TAT.js.map +1 -0
  48. package/dist/setup-YS27MOPE.js +124 -0
  49. package/dist/setup-YS27MOPE.js.map +1 -0
  50. package/dist/static/index.html +4815 -2007
  51. package/dist/store-WJ5Y7MOE.js +9 -0
  52. package/dist/store-WJ5Y7MOE.js.map +1 -0
  53. package/dist/{writer-4ZEAKUFD.js → writer-3NAVABN6.js} +3 -3
  54. package/dist/writer-3NAVABN6.js.map +1 -0
  55. package/package.json +77 -68
  56. package/dist/chunk-2RPTM6EQ.js.map +0 -1
  57. package/dist/chunk-A535VV7N.js.map +0 -1
  58. package/dist/chunk-DLP2O6PN.js.map +0 -1
  59. package/dist/chunk-EYO44OMN.js.map +0 -1
  60. package/dist/chunk-JYWQT2B4.js.map +0 -1
  61. package/dist/chunk-WPJJZLT6.js.map +0 -1
  62. /package/dist/{config-43SK6SFI.js.map → adapters-JAZGGNVP.js.map} +0 -0
  63. /package/dist/{db-SWJUUSFX.js.map → config-I5NCK3RJ.js.map} +0 -0
  64. /package/dist/{display-HFIFXOOL.js.map → copilot-cli-5WJWK5YT.js.map} +0 -0
  65. /package/dist/{ollama-HY35OHW4.js.map → db-ETWTBXAE.js.map} +0 -0
  66. /package/dist/{writer-4ZEAKUFD.js.map → display-UH7KEHOW.js.map} +0 -0
@@ -156,6 +156,95 @@ Fixed command:`,
156
156
  return null;
157
157
  }
158
158
  }
159
+ /**
160
+ * Show detailed info about a model
161
+ */
162
+ async showModel(name) {
163
+ try {
164
+ const response = await fetch(`${this.config.host}/api/show`, {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify({ name })
168
+ });
169
+ if (!response.ok) {
170
+ return null;
171
+ }
172
+ const data = await response.json();
173
+ return data;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+ /**
179
+ * Delete a model
180
+ */
181
+ async deleteModel(name) {
182
+ try {
183
+ const response = await fetch(`${this.config.host}/api/delete`, {
184
+ method: "DELETE",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify({ name })
187
+ });
188
+ return response.ok;
189
+ } catch {
190
+ return false;
191
+ }
192
+ }
193
+ /**
194
+ * List currently running models
195
+ */
196
+ async listRunning() {
197
+ try {
198
+ const response = await fetch(`${this.config.host}/api/ps`);
199
+ if (!response.ok) {
200
+ return [];
201
+ }
202
+ const data = await response.json();
203
+ return data.models || [];
204
+ } catch {
205
+ return [];
206
+ }
207
+ }
208
+ /**
209
+ * Pull a model from the registry
210
+ */
211
+ async pullModel(name) {
212
+ const controller = new AbortController();
213
+ const timeout = setTimeout(() => controller.abort(), 3e5);
214
+ try {
215
+ const response = await fetch(`${this.config.host}/api/pull`, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify({ name, stream: false }),
219
+ signal: controller.signal
220
+ });
221
+ clearTimeout(timeout);
222
+ return response.ok;
223
+ } catch {
224
+ clearTimeout(timeout);
225
+ return false;
226
+ }
227
+ }
228
+ /**
229
+ * Create a model from a Modelfile
230
+ */
231
+ async createModel(name, modelfile) {
232
+ const controller = new AbortController();
233
+ const timeout = setTimeout(() => controller.abort(), 12e4);
234
+ try {
235
+ const response = await fetch(`${this.config.host}/api/create`, {
236
+ method: "POST",
237
+ headers: { "Content-Type": "application/json" },
238
+ body: JSON.stringify({ name, modelfile, stream: false }),
239
+ signal: controller.signal
240
+ });
241
+ clearTimeout(timeout);
242
+ return response.ok;
243
+ } catch {
244
+ clearTimeout(timeout);
245
+ return false;
246
+ }
247
+ }
159
248
  setModel(model) {
160
249
  this.config.model = model;
161
250
  }
@@ -265,9 +354,21 @@ If you can't convert it, respond with "none".`;
265
354
  return null;
266
355
  }
267
356
  }
357
+ /**
358
+ * Generate with a temporary model override (for adapter-specific calls)
359
+ */
360
+ async generateWithAdapter(modelOverride, prompt, systemPrompt) {
361
+ const originalModel = this.config.model;
362
+ this.config.model = modelOverride;
363
+ try {
364
+ return await this.generate(prompt, systemPrompt);
365
+ } finally {
366
+ this.config.model = originalModel;
367
+ }
368
+ }
268
369
  };
269
370
 
270
371
  export {
271
372
  OllamaClient
272
373
  };
273
- //# sourceMappingURL=chunk-DLP2O6PN.js.map
374
+ //# sourceMappingURL=chunk-EMLEJVJZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bro/ollama.ts"],"sourcesContent":["/**\n * Simple Ollama client for local model inference.\n * Keeps it minimal - just what we need for Bash Bro.\n */\n\nexport interface OllamaConfig {\n host: string\n model: string\n timeout: number\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\nexport interface GenerateResponse {\n response: string\n done: boolean\n context?: number[]\n}\n\nexport interface ModelInfo {\n modelfile: string\n parameters: string\n template: string\n details: {\n parent_model: string\n format: string\n family: string\n families: string[]\n parameter_size: string\n quantization_level: string\n }\n}\n\nexport interface RunningModel {\n name: string\n model: string\n size: number\n size_vram: number\n digest: string\n details: {\n family: string\n parameter_size: string\n quantization_level: string\n }\n expires_at: string\n}\n\nconst DEFAULT_CONFIG: OllamaConfig = {\n host: 'http://localhost:11434',\n model: 'qwen2.5-coder:7b',\n timeout: 30000\n}\n\nexport class OllamaClient {\n private config: OllamaConfig\n\n constructor(config: Partial<OllamaConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * Check if Ollama is running and accessible\n */\n async isAvailable(): Promise<boolean> {\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n\n const response = await fetch(`${this.config.host}/api/tags`, {\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n return false\n }\n }\n\n /**\n * List available models\n */\n async listModels(): Promise<string[]> {\n try {\n const response = await fetch(`${this.config.host}/api/tags`)\n\n if (!response.ok) {\n return []\n }\n\n const data = await response.json() as { models?: { name: string }[] }\n return data.models?.map((m) => m.name) || []\n } catch {\n return []\n }\n }\n\n /**\n * Generate a response from the model\n */\n async generate(prompt: string, systemPrompt?: string): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.config.timeout)\n\n try {\n const response = await fetch(`${this.config.host}/api/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n prompt,\n system: systemPrompt,\n stream: false\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n\n if (!response.ok) {\n throw new Error(`Ollama error: ${response.status}`)\n }\n\n const data = await response.json() as GenerateResponse\n return data.response\n } catch (error: any) {\n if (error.name === 'AbortError') {\n throw new Error('Ollama request timed out')\n }\n throw error\n }\n }\n\n /**\n * Chat with the model (multi-turn conversation)\n */\n async chat(messages: ChatMessage[]): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.config.timeout)\n\n try {\n const response = await fetch(`${this.config.host}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n messages,\n stream: false\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n\n if (!response.ok) {\n throw new Error(`Ollama error: ${response.status}`)\n }\n\n const data = await response.json() as { message?: { content: string } }\n return data.message?.content || ''\n } catch (error: any) {\n if (error.name === 'AbortError') {\n throw new Error('Ollama request timed out')\n }\n throw error\n }\n }\n\n /**\n * Ask Bash Bro to suggest a command\n */\n async suggestCommand(context: string): Promise<string | null> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nGiven the context, suggest the most likely next command the user needs.\nRespond with ONLY the command, no explanation. If unsure, respond with \"none\".`\n\n try {\n const response = await this.generate(context, systemPrompt)\n const command = response.trim()\n\n if (command.toLowerCase() === 'none' || command.length > 200) {\n return null\n }\n\n return command\n } catch {\n return null\n }\n }\n\n /**\n * Ask Bash Bro to explain a command\n */\n async explainCommand(command: string): Promise<string> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nExplain what the given command does in 1-2 sentences. Be concise and accurate.`\n\n try {\n return await this.generate(`Explain: ${command}`, systemPrompt)\n } catch {\n return 'Could not explain command.'\n }\n }\n\n /**\n * Ask Bash Bro to fix a command that failed\n */\n async fixCommand(command: string, error: string): Promise<string | null> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nGiven a failed command and its error, suggest a fixed version.\nRespond with ONLY the fixed command, no explanation. If you can't fix it, respond with \"none\".`\n\n try {\n const response = await this.generate(\n `Command: ${command}\\nError: ${error}\\nFixed command:`,\n systemPrompt\n )\n\n const fixed = response.trim()\n\n if (fixed.toLowerCase() === 'none' || fixed.length > 500) {\n return null\n }\n\n return fixed\n } catch {\n return null\n }\n }\n\n /**\n * Show detailed info about a model\n */\n async showModel(name: string): Promise<ModelInfo | null> {\n try {\n const response = await fetch(`${this.config.host}/api/show`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name })\n })\n\n if (!response.ok) {\n return null\n }\n\n const data = await response.json() as ModelInfo\n return data\n } catch {\n return null\n }\n }\n\n /**\n * Delete a model\n */\n async deleteModel(name: string): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.host}/api/delete`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name })\n })\n\n return response.ok\n } catch {\n return false\n }\n }\n\n /**\n * List currently running models\n */\n async listRunning(): Promise<RunningModel[]> {\n try {\n const response = await fetch(`${this.config.host}/api/ps`)\n\n if (!response.ok) {\n return []\n }\n\n const data = await response.json() as { models?: RunningModel[] }\n return data.models || []\n } catch {\n return []\n }\n }\n\n /**\n * Pull a model from the registry\n */\n async pullModel(name: string): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 300000) // 5 minutes\n\n try {\n const response = await fetch(`${this.config.host}/api/pull`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, stream: false }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n clearTimeout(timeout)\n return false\n }\n }\n\n /**\n * Create a model from a Modelfile\n */\n async createModel(name: string, modelfile: string): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 120000) // 2 minutes\n\n try {\n const response = await fetch(`${this.config.host}/api/create`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, modelfile, stream: false }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n clearTimeout(timeout)\n return false\n }\n }\n\n setModel(model: string): void {\n this.config.model = model\n }\n\n getModel(): string {\n return this.config.model\n }\n\n /**\n * Generate a shell script from a natural language description\n */\n async generateScript(description: string, shell: string = 'bash'): Promise<string | null> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nGenerate a ${shell} script based on the user's description.\nOutput ONLY the script, no explanation. Start with the shebang line.\nKeep scripts simple, readable, and well-commented.`\n\n try {\n const response = await this.generate(\n `Generate a ${shell} script that: ${description}`,\n systemPrompt\n )\n return response.trim()\n } catch {\n return null\n }\n }\n\n /**\n * Analyze command safety and provide recommendations\n */\n async analyzeCommandSafety(command: string): Promise<{\n safe: boolean\n risk: 'low' | 'medium' | 'high' | 'critical'\n explanation: string\n suggestions: string[]\n }> {\n const systemPrompt = `You are Bash Bro, a security-focused command-line assistant.\nAnalyze the given command for security risks.\nRespond with JSON only, in this format:\n{\"safe\": boolean, \"risk\": \"low|medium|high|critical\", \"explanation\": \"...\", \"suggestions\": [\"...\"]}`\n\n try {\n const response = await this.generate(\n `Analyze this command for security risks: ${command}`,\n systemPrompt\n )\n\n // Try to parse JSON from response\n const jsonMatch = response.match(/\\{[\\s\\S]*\\}/)\n if (jsonMatch) {\n return JSON.parse(jsonMatch[0])\n }\n\n // Fallback if not parseable\n return {\n safe: true,\n risk: 'low',\n explanation: 'Could not analyze command.',\n suggestions: []\n }\n } catch {\n return {\n safe: true,\n risk: 'low',\n explanation: 'Analysis unavailable.',\n suggestions: []\n }\n }\n }\n\n /**\n * Summarize a series of commands and their outputs\n */\n async summarizeSession(commands: { command: string; output?: string; error?: string }[]): Promise<string> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nSummarize what happened in this terminal session in 2-3 sentences.\nFocus on what was accomplished and any issues encountered.`\n\n const sessionText = commands.map(c => {\n let text = `$ ${c.command}`\n if (c.output) text += `\\n${c.output.slice(0, 500)}`\n if (c.error) text += `\\nError: ${c.error.slice(0, 200)}`\n return text\n }).join('\\n\\n')\n\n try {\n return await this.generate(sessionText, systemPrompt)\n } catch {\n return 'Could not summarize session.'\n }\n }\n\n /**\n * Get help for a specific tool or command\n */\n async getHelp(topic: string): Promise<string> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nProvide concise help about the requested command or topic.\nInclude common usage examples. Keep it practical and brief.`\n\n try {\n return await this.generate(`Help with: ${topic}`, systemPrompt)\n } catch {\n return 'Could not get help for this topic.'\n }\n }\n\n /**\n * Convert natural language to a command\n */\n async naturalToCommand(description: string): Promise<string | null> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nConvert the user's request into a single command line.\nRespond with ONLY the command, no explanation.\nIf you can't convert it, respond with \"none\".`\n\n try {\n const response = await this.generate(description, systemPrompt)\n const command = response.trim()\n\n if (command.toLowerCase() === 'none' || command.length > 300) {\n return null\n }\n\n return command\n } catch {\n return null\n }\n }\n\n /**\n * Generate with a temporary model override (for adapter-specific calls)\n */\n async generateWithAdapter(modelOverride: string, prompt: string, systemPrompt?: string): Promise<string> {\n const originalModel = this.config.model\n this.config.model = modelOverride\n try {\n return await this.generate(prompt, systemPrompt)\n } finally {\n this.config.model = originalModel\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/opencode.ts
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
5
+ import { join } from "path";
6
+ import { execFileSync } from "child_process";
7
+ var OPENCODE_DIR_NAME = ".opencode";
8
+ var PLUGINS_DIR_NAME = "plugins";
9
+ var PLUGIN_FILENAME = "bashbros.ts";
10
+ var BASHBROS_MANAGED_MARKER = "// bashbros-managed";
11
+ var OpenCodeHooks = class {
12
+ /**
13
+ * Check if OpenCode is available (project .opencode dir exists or opencode command on PATH)
14
+ */
15
+ static isOpenCodeInstalled(projectDir) {
16
+ const dir = projectDir || process.cwd();
17
+ const openCodeDir = join(dir, OPENCODE_DIR_NAME);
18
+ if (existsSync(openCodeDir)) {
19
+ return true;
20
+ }
21
+ try {
22
+ const cmd = process.platform === "win32" ? "where" : "which";
23
+ execFileSync(cmd, ["opencode"], { stdio: "pipe", timeout: 3e3 });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Get the path to the plugin file
31
+ */
32
+ static getPluginPath(projectDir) {
33
+ const dir = projectDir || process.cwd();
34
+ return join(dir, OPENCODE_DIR_NAME, PLUGINS_DIR_NAME, PLUGIN_FILENAME);
35
+ }
36
+ /**
37
+ * Get the path to the plugins directory
38
+ */
39
+ static getPluginsDir(projectDir) {
40
+ const dir = projectDir || process.cwd();
41
+ return join(dir, OPENCODE_DIR_NAME, PLUGINS_DIR_NAME);
42
+ }
43
+ /**
44
+ * Generate the TypeScript plugin source code
45
+ */
46
+ static generatePluginSource() {
47
+ return `// bashbros-managed - BashBros security plugin for OpenCode
48
+ // Do not edit manually. Managed by: bashbros opencode install
49
+ import type { Plugin } from "@opencode-ai/plugin"
50
+
51
+ export const BashBrosPlugin: Plugin = async ({ $ }) => {
52
+ return {
53
+ "tool.execute.before": async (input: any, output: any) => {
54
+ if (input.tool === "bash") {
55
+ const command = typeof output.args?.command === 'string' ? output.args.command : ''
56
+ if (!command) return
57
+ try {
58
+ await $\`bashbros gate \${JSON.stringify(command)}\`
59
+ } catch (error: any) {
60
+ throw new Error(\`BashBros blocked: \${error?.stderr || error?.message || 'Policy violation'}\`)
61
+ }
62
+ }
63
+ },
64
+ "tool.execute.after": async (input: any) => {
65
+ if (input.tool === "bash") {
66
+ try {
67
+ await $\`bashbros record \${JSON.stringify(JSON.stringify({ tool: input.tool, args: input.args }))}\`
68
+ } catch {
69
+ // Silent fail for recording
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ `;
76
+ }
77
+ /**
78
+ * Install BashBros plugin into OpenCode project
79
+ */
80
+ static install(projectDir) {
81
+ if (!this.isOpenCodeInstalled(projectDir)) {
82
+ return {
83
+ success: false,
84
+ message: "OpenCode not found. Install OpenCode or initialize a .opencode directory first."
85
+ };
86
+ }
87
+ if (this.isInstalled(projectDir)) {
88
+ return {
89
+ success: true,
90
+ message: "BashBros plugin already installed."
91
+ };
92
+ }
93
+ const pluginsDir = this.getPluginsDir(projectDir);
94
+ const pluginPath = this.getPluginPath(projectDir);
95
+ if (!existsSync(pluginsDir)) {
96
+ mkdirSync(pluginsDir, { recursive: true });
97
+ }
98
+ writeFileSync(pluginPath, this.generatePluginSource(), "utf-8");
99
+ return {
100
+ success: true,
101
+ message: "BashBros plugin installed successfully."
102
+ };
103
+ }
104
+ /**
105
+ * Uninstall BashBros plugin from OpenCode project
106
+ */
107
+ static uninstall(projectDir) {
108
+ const pluginPath = this.getPluginPath(projectDir);
109
+ if (!existsSync(pluginPath)) {
110
+ return {
111
+ success: true,
112
+ message: "No BashBros plugin found. Nothing to uninstall."
113
+ };
114
+ }
115
+ try {
116
+ const content = readFileSync(pluginPath, "utf-8");
117
+ if (!content.startsWith(BASHBROS_MANAGED_MARKER)) {
118
+ return {
119
+ success: false,
120
+ message: "Plugin file exists but is not managed by BashBros. Refusing to remove user-created plugin."
121
+ };
122
+ }
123
+ } catch {
124
+ return {
125
+ success: false,
126
+ message: "Failed to read plugin file."
127
+ };
128
+ }
129
+ unlinkSync(pluginPath);
130
+ return {
131
+ success: true,
132
+ message: "BashBros plugin uninstalled successfully."
133
+ };
134
+ }
135
+ /**
136
+ * Check if BashBros plugin is installed with the managed marker
137
+ */
138
+ static isInstalled(projectDir) {
139
+ const pluginPath = this.getPluginPath(projectDir);
140
+ if (!existsSync(pluginPath)) {
141
+ return false;
142
+ }
143
+ try {
144
+ const content = readFileSync(pluginPath, "utf-8");
145
+ return content.startsWith(BASHBROS_MANAGED_MARKER);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ /**
151
+ * Get plugin status
152
+ */
153
+ static getStatus(projectDir) {
154
+ const openCodeInstalled = this.isOpenCodeInstalled(projectDir);
155
+ const pluginInstalled = this.isInstalled(projectDir);
156
+ return {
157
+ openCodeInstalled,
158
+ pluginInstalled
159
+ };
160
+ }
161
+ };
162
+
163
+ export {
164
+ OpenCodeHooks
165
+ };
166
+ //# sourceMappingURL=chunk-IUUBCPMV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/opencode.ts"],"sourcesContent":["/**\r\n * OpenCode Hook Integration\r\n * Seamlessly integrate BashBros with OpenCode via TypeScript plugin modules\r\n */\r\n\r\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { execFileSync } from 'child_process'\r\n\r\nconst OPENCODE_DIR_NAME = '.opencode'\r\nconst PLUGINS_DIR_NAME = 'plugins'\r\nconst PLUGIN_FILENAME = 'bashbros.ts'\r\nconst BASHBROS_MANAGED_MARKER = '// bashbros-managed'\r\n\r\nexport class OpenCodeHooks {\r\n /**\r\n * Check if OpenCode is available (project .opencode dir exists or opencode command on PATH)\r\n */\r\n static isOpenCodeInstalled(projectDir?: string): boolean {\r\n const dir = projectDir || process.cwd()\r\n const openCodeDir = join(dir, OPENCODE_DIR_NAME)\r\n\r\n // Check for .opencode directory in the project\r\n if (existsSync(openCodeDir)) {\r\n return true\r\n }\r\n\r\n // Check for opencode command on PATH\r\n try {\r\n const cmd = process.platform === 'win32' ? 'where' : 'which'\r\n execFileSync(cmd, ['opencode'], { stdio: 'pipe', timeout: 3000 })\r\n return true\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /**\r\n * Get the path to the plugin file\r\n */\r\n private static getPluginPath(projectDir?: string): string {\r\n const dir = projectDir || process.cwd()\r\n return join(dir, OPENCODE_DIR_NAME, PLUGINS_DIR_NAME, PLUGIN_FILENAME)\r\n }\r\n\r\n /**\r\n * Get the path to the plugins directory\r\n */\r\n private static getPluginsDir(projectDir?: string): string {\r\n const dir = projectDir || process.cwd()\r\n return join(dir, OPENCODE_DIR_NAME, PLUGINS_DIR_NAME)\r\n }\r\n\r\n /**\r\n * Generate the TypeScript plugin source code\r\n */\r\n static generatePluginSource(): string {\r\n return `// bashbros-managed - BashBros security plugin for OpenCode\r\n// Do not edit manually. Managed by: bashbros opencode install\r\nimport type { Plugin } from \"@opencode-ai/plugin\"\r\n\r\nexport const BashBrosPlugin: Plugin = async ({ $ }) => {\r\n return {\r\n \"tool.execute.before\": async (input: any, output: any) => {\r\n if (input.tool === \"bash\") {\r\n const command = typeof output.args?.command === 'string' ? output.args.command : ''\r\n if (!command) return\r\n try {\r\n await $\\`bashbros gate \\${JSON.stringify(command)}\\`\r\n } catch (error: any) {\r\n throw new Error(\\`BashBros blocked: \\${error?.stderr || error?.message || 'Policy violation'}\\`)\r\n }\r\n }\r\n },\r\n \"tool.execute.after\": async (input: any) => {\r\n if (input.tool === \"bash\") {\r\n try {\r\n await $\\`bashbros record \\${JSON.stringify(JSON.stringify({ tool: input.tool, args: input.args }))}\\`\r\n } catch {\r\n // Silent fail for recording\r\n }\r\n }\r\n }\r\n }\r\n}\r\n`\r\n }\r\n\r\n /**\r\n * Install BashBros plugin into OpenCode project\r\n */\r\n static install(projectDir?: string): { success: boolean; message: string } {\r\n if (!this.isOpenCodeInstalled(projectDir)) {\r\n return {\r\n success: false,\r\n message: 'OpenCode not found. Install OpenCode or initialize a .opencode directory first.'\r\n }\r\n }\r\n\r\n // Check if already installed\r\n if (this.isInstalled(projectDir)) {\r\n return {\r\n success: true,\r\n message: 'BashBros plugin already installed.'\r\n }\r\n }\r\n\r\n const pluginsDir = this.getPluginsDir(projectDir)\r\n const pluginPath = this.getPluginPath(projectDir)\r\n\r\n // Ensure plugins directory exists\r\n if (!existsSync(pluginsDir)) {\r\n mkdirSync(pluginsDir, { recursive: true })\r\n }\r\n\r\n // Write the plugin file\r\n writeFileSync(pluginPath, this.generatePluginSource(), 'utf-8')\r\n\r\n return {\r\n success: true,\r\n message: 'BashBros plugin installed successfully.'\r\n }\r\n }\r\n\r\n /**\r\n * Uninstall BashBros plugin from OpenCode project\r\n */\r\n static uninstall(projectDir?: string): { success: boolean; message: string } {\r\n const pluginPath = this.getPluginPath(projectDir)\r\n\r\n if (!existsSync(pluginPath)) {\r\n return {\r\n success: true,\r\n message: 'No BashBros plugin found. Nothing to uninstall.'\r\n }\r\n }\r\n\r\n // Read the file to verify it has the bashbros-managed marker\r\n try {\r\n const content = readFileSync(pluginPath, 'utf-8')\r\n if (!content.startsWith(BASHBROS_MANAGED_MARKER)) {\r\n return {\r\n success: false,\r\n message: 'Plugin file exists but is not managed by BashBros. Refusing to remove user-created plugin.'\r\n }\r\n }\r\n } catch {\r\n return {\r\n success: false,\r\n message: 'Failed to read plugin file.'\r\n }\r\n }\r\n\r\n // Safe to remove - it has the bashbros-managed marker\r\n unlinkSync(pluginPath)\r\n\r\n return {\r\n success: true,\r\n message: 'BashBros plugin uninstalled successfully.'\r\n }\r\n }\r\n\r\n /**\r\n * Check if BashBros plugin is installed with the managed marker\r\n */\r\n static isInstalled(projectDir?: string): boolean {\r\n const pluginPath = this.getPluginPath(projectDir)\r\n\r\n if (!existsSync(pluginPath)) {\r\n return false\r\n }\r\n\r\n try {\r\n const content = readFileSync(pluginPath, 'utf-8')\r\n return content.startsWith(BASHBROS_MANAGED_MARKER)\r\n } catch {\r\n return false\r\n }\r\n }\r\n\r\n /**\r\n * Get plugin status\r\n */\r\n static getStatus(projectDir?: string): {\r\n openCodeInstalled: boolean\r\n pluginInstalled: boolean\r\n } {\r\n const openCodeInstalled = this.isOpenCodeInstalled(projectDir)\r\n const pluginInstalled = this.isInstalled(projectDir)\r\n\r\n return {\r\n openCodeInstalled,\r\n pluginInstalled\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/context/store.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, readdirSync, unlinkSync } from "fs";
5
+ import { join } from "path";
6
+ var ContextStore = class {
7
+ projectRoot;
8
+ contextDir;
9
+ memoryDir;
10
+ artifactsDir;
11
+ constructor(projectRoot) {
12
+ this.projectRoot = projectRoot;
13
+ this.contextDir = join(projectRoot, ".bashbros", "context");
14
+ this.memoryDir = join(this.contextDir, "memory");
15
+ this.artifactsDir = join(this.contextDir, "artifacts");
16
+ }
17
+ initialize() {
18
+ const dirs = [
19
+ this.memoryDir,
20
+ join(this.memoryDir, "custom"),
21
+ join(this.artifactsDir, "sessions"),
22
+ join(this.artifactsDir, "commands"),
23
+ join(this.artifactsDir, "errors")
24
+ ];
25
+ for (const dir of dirs) {
26
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
27
+ }
28
+ const starters = {
29
+ "decisions.md": "# Decisions\n\nArchitectural decisions recorded during sessions.\n",
30
+ "conventions.md": "# Conventions\n\nCoding patterns and style choices for this project.\n",
31
+ "issues.md": "# Known Issues\n\nGotchas, workarounds, and known problems.\n"
32
+ };
33
+ for (const [file, content] of Object.entries(starters)) {
34
+ const filePath = join(this.memoryDir, file);
35
+ if (!existsSync(filePath)) writeFileSync(filePath, content);
36
+ }
37
+ const indexPath = join(this.contextDir, "index.json");
38
+ if (!existsSync(indexPath)) {
39
+ writeFileSync(indexPath, JSON.stringify({
40
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
41
+ agents: [],
42
+ sessionCount: 0,
43
+ commandFileCount: 0,
44
+ errorFileCount: 0
45
+ }, null, 2));
46
+ }
47
+ }
48
+ appendCommand(entry) {
49
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
50
+ appendFileSync(
51
+ join(this.artifactsDir, "commands", `${today}.jsonl`),
52
+ JSON.stringify({ ...entry, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n"
53
+ );
54
+ }
55
+ appendError(entry) {
56
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
57
+ appendFileSync(
58
+ join(this.artifactsDir, "errors", `${today}.jsonl`),
59
+ JSON.stringify({ ...entry, timestamp: (/* @__PURE__ */ new Date()).toISOString() }) + "\n"
60
+ );
61
+ }
62
+ writeSession(session) {
63
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
64
+ const filename = `${timestamp}-${session.agent}-${session.id.slice(-8)}.json`;
65
+ writeFileSync(
66
+ join(this.artifactsDir, "sessions", filename),
67
+ JSON.stringify(session, null, 2)
68
+ );
69
+ }
70
+ readMemory(name) {
71
+ const filePath = join(this.memoryDir, `${name}.md`);
72
+ if (!existsSync(filePath)) return null;
73
+ return readFileSync(filePath, "utf-8");
74
+ }
75
+ writeMemory(name, content) {
76
+ writeFileSync(join(this.memoryDir, `${name}.md`), content);
77
+ }
78
+ listMemoryFiles() {
79
+ if (!existsSync(this.memoryDir)) return [];
80
+ return readdirSync(this.memoryDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
81
+ }
82
+ updateIndex() {
83
+ const agents = /* @__PURE__ */ new Set();
84
+ const sessionsDir = join(this.artifactsDir, "sessions");
85
+ if (existsSync(sessionsDir)) {
86
+ for (const file of readdirSync(sessionsDir)) {
87
+ try {
88
+ const d = JSON.parse(readFileSync(join(sessionsDir, file), "utf-8"));
89
+ if (d.agent) agents.add(d.agent);
90
+ } catch {
91
+ }
92
+ }
93
+ }
94
+ const commandsDir = join(this.artifactsDir, "commands");
95
+ if (existsSync(commandsDir)) {
96
+ for (const file of readdirSync(commandsDir)) {
97
+ try {
98
+ for (const line of readFileSync(join(commandsDir, file), "utf-8").trim().split("\n")) {
99
+ const d = JSON.parse(line);
100
+ if (d.agent) agents.add(d.agent);
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ const sessionCount = existsSync(sessionsDir) ? readdirSync(sessionsDir).length : 0;
107
+ const commandFileCount = existsSync(commandsDir) ? readdirSync(commandsDir).length : 0;
108
+ const errorsDir = join(this.artifactsDir, "errors");
109
+ const errorFileCount = existsSync(errorsDir) ? readdirSync(errorsDir).length : 0;
110
+ writeFileSync(join(this.contextDir, "index.json"), JSON.stringify({
111
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
112
+ agents: [...agents],
113
+ sessionCount,
114
+ commandFileCount,
115
+ errorFileCount
116
+ }, null, 2));
117
+ }
118
+ getIndex() {
119
+ const indexPath = join(this.contextDir, "index.json");
120
+ if (!existsSync(indexPath)) {
121
+ return { lastUpdated: "", agents: [], sessionCount: 0, commandFileCount: 0, errorFileCount: 0 };
122
+ }
123
+ return JSON.parse(readFileSync(indexPath, "utf-8"));
124
+ }
125
+ prune(retentionDays) {
126
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
127
+ for (const subdir of ["commands", "errors"]) {
128
+ const dir = join(this.artifactsDir, subdir);
129
+ if (!existsSync(dir)) continue;
130
+ for (const file of readdirSync(dir)) {
131
+ const dateStr = file.replace(".jsonl", "");
132
+ if (new Date(dateStr).getTime() < cutoff) {
133
+ unlinkSync(join(dir, file));
134
+ }
135
+ }
136
+ }
137
+ }
138
+ getContextDir() {
139
+ return this.contextDir;
140
+ }
141
+ };
142
+
143
+ export {
144
+ ContextStore
145
+ };
146
+ //# sourceMappingURL=chunk-J6ONXY6N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, readdirSync, unlinkSync } from 'fs'\nimport { join } from 'path'\n\nexport interface CommandEntry {\n command: string\n output: string\n agent: string\n exitCode: number\n cwd: string\n}\n\nexport interface ErrorEntry {\n command: string\n error: string\n agent: string\n resolved: boolean\n}\n\nexport interface SessionSummary {\n id: string\n agent: string\n startTime: string\n endTime: string\n commandCount: number\n summary: string\n}\n\nexport interface ContextIndex {\n lastUpdated: string\n agents: string[]\n sessionCount: number\n commandFileCount: number\n errorFileCount: number\n}\n\nexport class ContextStore {\n private projectRoot: string\n private contextDir: string\n private memoryDir: string\n private artifactsDir: string\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot\n this.contextDir = join(projectRoot, '.bashbros', 'context')\n this.memoryDir = join(this.contextDir, 'memory')\n this.artifactsDir = join(this.contextDir, 'artifacts')\n }\n\n initialize(): void {\n const dirs = [\n this.memoryDir,\n join(this.memoryDir, 'custom'),\n join(this.artifactsDir, 'sessions'),\n join(this.artifactsDir, 'commands'),\n join(this.artifactsDir, 'errors')\n ]\n for (const dir of dirs) {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n }\n\n const starters: Record<string, string> = {\n 'decisions.md': '# Decisions\\n\\nArchitectural decisions recorded during sessions.\\n',\n 'conventions.md': '# Conventions\\n\\nCoding patterns and style choices for this project.\\n',\n 'issues.md': '# Known Issues\\n\\nGotchas, workarounds, and known problems.\\n'\n }\n for (const [file, content] of Object.entries(starters)) {\n const filePath = join(this.memoryDir, file)\n if (!existsSync(filePath)) writeFileSync(filePath, content)\n }\n\n const indexPath = join(this.contextDir, 'index.json')\n if (!existsSync(indexPath)) {\n writeFileSync(indexPath, JSON.stringify({\n lastUpdated: new Date().toISOString(),\n agents: [],\n sessionCount: 0,\n commandFileCount: 0,\n errorFileCount: 0\n }, null, 2))\n }\n }\n\n appendCommand(entry: CommandEntry): void {\n const today = new Date().toISOString().slice(0, 10)\n appendFileSync(\n join(this.artifactsDir, 'commands', `${today}.jsonl`),\n JSON.stringify({ ...entry, timestamp: new Date().toISOString() }) + '\\n'\n )\n }\n\n appendError(entry: ErrorEntry): void {\n const today = new Date().toISOString().slice(0, 10)\n appendFileSync(\n join(this.artifactsDir, 'errors', `${today}.jsonl`),\n JSON.stringify({ ...entry, timestamp: new Date().toISOString() }) + '\\n'\n )\n }\n\n writeSession(session: SessionSummary): void {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-')\n const filename = `${timestamp}-${session.agent}-${session.id.slice(-8)}.json`\n writeFileSync(\n join(this.artifactsDir, 'sessions', filename),\n JSON.stringify(session, null, 2)\n )\n }\n\n readMemory(name: string): string | null {\n const filePath = join(this.memoryDir, `${name}.md`)\n if (!existsSync(filePath)) return null\n return readFileSync(filePath, 'utf-8')\n }\n\n writeMemory(name: string, content: string): void {\n writeFileSync(join(this.memoryDir, `${name}.md`), content)\n }\n\n listMemoryFiles(): string[] {\n if (!existsSync(this.memoryDir)) return []\n return readdirSync(this.memoryDir)\n .filter(f => f.endsWith('.md'))\n .map(f => f.replace('.md', ''))\n }\n\n updateIndex(): void {\n const agents = new Set<string>()\n\n const sessionsDir = join(this.artifactsDir, 'sessions')\n if (existsSync(sessionsDir)) {\n for (const file of readdirSync(sessionsDir)) {\n try {\n const d = JSON.parse(readFileSync(join(sessionsDir, file), 'utf-8'))\n if (d.agent) agents.add(d.agent)\n } catch { /* ignore malformed files */ }\n }\n }\n\n const commandsDir = join(this.artifactsDir, 'commands')\n if (existsSync(commandsDir)) {\n for (const file of readdirSync(commandsDir)) {\n try {\n for (const line of readFileSync(join(commandsDir, file), 'utf-8').trim().split('\\n')) {\n const d = JSON.parse(line)\n if (d.agent) agents.add(d.agent)\n }\n } catch { /* ignore malformed files */ }\n }\n }\n\n const sessionCount = existsSync(sessionsDir) ? readdirSync(sessionsDir).length : 0\n const commandFileCount = existsSync(commandsDir) ? readdirSync(commandsDir).length : 0\n const errorsDir = join(this.artifactsDir, 'errors')\n const errorFileCount = existsSync(errorsDir) ? readdirSync(errorsDir).length : 0\n\n writeFileSync(join(this.contextDir, 'index.json'), JSON.stringify({\n lastUpdated: new Date().toISOString(),\n agents: [...agents],\n sessionCount,\n commandFileCount,\n errorFileCount\n }, null, 2))\n }\n\n getIndex(): ContextIndex {\n const indexPath = join(this.contextDir, 'index.json')\n if (!existsSync(indexPath)) {\n return { lastUpdated: '', agents: [], sessionCount: 0, commandFileCount: 0, errorFileCount: 0 }\n }\n return JSON.parse(readFileSync(indexPath, 'utf-8'))\n }\n\n prune(retentionDays: number): void {\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000\n for (const subdir of ['commands', 'errors']) {\n const dir = join(this.artifactsDir, subdir)\n if (!existsSync(dir)) continue\n for (const file of readdirSync(dir)) {\n const dateStr = file.replace('.jsonl', '')\n if (new Date(dateStr).getTime() < cutoff) {\n unlinkSync(join(dir, file))\n }\n }\n }\n }\n\n getContextDir(): string {\n return this.contextDir\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DashboardDB
4
- } from "./chunk-JYWQT2B4.js";
4
+ } from "./chunk-RDNSS3ME.js";
5
5
 
6
6
  // src/dashboard/writer.ts
7
7
  import { homedir } from "os";
@@ -20,6 +20,7 @@ var DashboardWriter = class {
20
20
  commandCount = 0;
21
21
  blockedCount = 0;
22
22
  totalRiskScore = 0;
23
+ hookMode = false;
23
24
  constructor(dbPath) {
24
25
  const path = dbPath ?? getDefaultDbPath();
25
26
  this.db = new DashboardDB(path);
@@ -38,6 +39,34 @@ var DashboardWriter = class {
38
39
  this.totalRiskScore = 0;
39
40
  return this.sessionId;
40
41
  }
42
+ /**
43
+ * Ensure a hook-mode session exists for the given external session ID.
44
+ * Idempotent - safe to call multiple times per hook invocation.
45
+ * Uses atomic DB increments so concurrent hook processes don't clobber each other.
46
+ */
47
+ ensureHookSession(hookSessionId, workingDir, repoName) {
48
+ this.db.insertSessionWithId(hookSessionId, {
49
+ agent: "claude-code",
50
+ pid: process.pid,
51
+ workingDir,
52
+ repoName: repoName ?? null,
53
+ mode: "hook"
54
+ });
55
+ this.sessionId = hookSessionId;
56
+ this.hookMode = true;
57
+ }
58
+ /**
59
+ * End a hook-mode session by ID
60
+ */
61
+ endHookSession(hookSessionId) {
62
+ const session = this.db.getSession(hookSessionId);
63
+ if (session && session.status === "active") {
64
+ this.db.updateSession(hookSessionId, {
65
+ endTime: /* @__PURE__ */ new Date(),
66
+ status: "completed"
67
+ });
68
+ }
69
+ }
41
70
  /**
42
71
  * End the current session
43
72
  */
@@ -72,9 +101,8 @@ var DashboardWriter = class {
72
101
  * Record a command execution
73
102
  */
74
103
  recordCommand(command, allowed, riskScore, violations, durationMs) {
75
- if (!this.sessionId) return null;
76
104
  const input = {
77
- sessionId: this.sessionId,
105
+ sessionId: this.sessionId ?? void 0,
78
106
  command,
79
107
  allowed,
80
108
  riskScore: riskScore.score,
@@ -84,18 +112,19 @@ var DashboardWriter = class {
84
112
  violations: violations.map((v) => v.message)
85
113
  };
86
114
  const id = this.db.insertCommand(input);
87
- this.commandCount++;
88
- this.totalRiskScore += riskScore.score;
89
- if (!allowed) {
90
- this.blockedCount++;
91
- }
92
- if (this.commandCount % 10 === 0) {
93
- const avgRiskScore = this.totalRiskScore / this.commandCount;
94
- this.db.updateSession(this.sessionId, {
95
- commandCount: this.commandCount,
96
- blockedCount: this.blockedCount,
97
- avgRiskScore
98
- });
115
+ if (this.sessionId) {
116
+ if (this.hookMode) {
117
+ this.db.incrementSessionCommand(this.sessionId, !allowed, riskScore.score);
118
+ } else {
119
+ this.commandCount++;
120
+ this.totalRiskScore += riskScore.score;
121
+ if (!allowed) {
122
+ this.blockedCount++;
123
+ }
124
+ if (this.commandCount % 10 === 0) {
125
+ this.flushSessionStats();
126
+ }
127
+ }
99
128
  }
100
129
  return id;
101
130
  }
@@ -139,7 +168,8 @@ var DashboardWriter = class {
139
168
  success: input.success,
140
169
  cwd: input.cwd,
141
170
  repoName: input.repoName,
142
- repoPath: input.repoPath
171
+ repoPath: input.repoPath,
172
+ sessionId: this.sessionId ?? void 0
143
173
  };
144
174
  return this.db.insertToolUse(dbInput);
145
175
  }
@@ -159,10 +189,23 @@ var DashboardWriter = class {
159
189
  avgRiskScore: this.commandCount > 0 ? this.totalRiskScore / this.commandCount : 0
160
190
  };
161
191
  }
192
+ /**
193
+ * Flush in-memory session stats to DB (watch mode only).
194
+ */
195
+ flushSessionStats() {
196
+ if (!this.sessionId || this.hookMode || this.commandCount === 0) return;
197
+ const avgRiskScore = this.totalRiskScore / this.commandCount;
198
+ this.db.updateSession(this.sessionId, {
199
+ commandCount: this.commandCount,
200
+ blockedCount: this.blockedCount,
201
+ avgRiskScore
202
+ });
203
+ }
162
204
  /**
163
205
  * Close database connection
164
206
  */
165
207
  close() {
208
+ this.flushSessionStats();
166
209
  this.db.close();
167
210
  }
168
211
  /**
@@ -178,4 +221,4 @@ export {
178
221
  DashboardWriter,
179
222
  writer_default
180
223
  };
181
- //# sourceMappingURL=chunk-EYO44OMN.js.map
224
+ //# sourceMappingURL=chunk-KYDMPE4N.js.map