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
@@ -4,10 +4,16 @@ import {
4
4
  } from "./chunk-SG752FZC.js";
5
5
  import {
6
6
  OllamaClient
7
- } from "./chunk-DLP2O6PN.js";
7
+ } from "./chunk-EMLEJVJZ.js";
8
+ import {
9
+ AdapterRegistry
10
+ } from "./chunk-4XZ64P4V.js";
11
+ import {
12
+ ProfileManager
13
+ } from "./chunk-LJE4EPIU.js";
8
14
  import {
9
15
  loadConfig
10
- } from "./chunk-A535VV7N.js";
16
+ } from "./chunk-RTZ4QWG2.js";
11
17
  import {
12
18
  PolicyEngine
13
19
  } from "./chunk-QWZGB4V3.js";
@@ -414,290 +420,6 @@ function resetBashgymIntegration() {
414
420
  _integration = null;
415
421
  }
416
422
 
417
- // src/hooks/claude-code.ts
418
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
419
- import { join as join2 } from "path";
420
- import { homedir as homedir2 } from "os";
421
- var CLAUDE_SETTINGS_PATH = join2(homedir2(), ".claude", "settings.json");
422
- var CLAUDE_DIR = join2(homedir2(), ".claude");
423
- var BASHBROS_HOOK_MARKER = "# bashbros-managed";
424
- var BASHBROS_ALL_TOOLS_MARKER = "--marker=bashbros-all-tools";
425
- var ClaudeCodeHooks = class {
426
- /**
427
- * Check if Claude Code is installed
428
- */
429
- static isClaudeInstalled() {
430
- return existsSync2(CLAUDE_DIR);
431
- }
432
- /**
433
- * Load current Claude settings
434
- */
435
- static loadSettings() {
436
- if (!existsSync2(CLAUDE_SETTINGS_PATH)) {
437
- return {};
438
- }
439
- try {
440
- const content = readFileSync2(CLAUDE_SETTINGS_PATH, "utf-8");
441
- return JSON.parse(content);
442
- } catch {
443
- return {};
444
- }
445
- }
446
- /**
447
- * Save Claude settings
448
- */
449
- static saveSettings(settings) {
450
- if (!existsSync2(CLAUDE_DIR)) {
451
- mkdirSync2(CLAUDE_DIR, { recursive: true });
452
- }
453
- writeFileSync2(
454
- CLAUDE_SETTINGS_PATH,
455
- JSON.stringify(settings, null, 2),
456
- "utf-8"
457
- );
458
- }
459
- /**
460
- * Install BashBros hooks into Claude Code
461
- */
462
- static install() {
463
- if (!this.isClaudeInstalled()) {
464
- return {
465
- success: false,
466
- message: "Claude Code not found. Install Claude Code first."
467
- };
468
- }
469
- const settings = this.loadSettings();
470
- if (!settings.hooks) {
471
- settings.hooks = {};
472
- }
473
- if (this.isInstalled(settings)) {
474
- return {
475
- success: true,
476
- message: "BashBros hooks already installed."
477
- };
478
- }
479
- const preToolUseHook = {
480
- matcher: "Bash",
481
- hooks: [{
482
- type: "command",
483
- command: `bashbros gate "$TOOL_INPUT" ${BASHBROS_HOOK_MARKER}`
484
- }]
485
- };
486
- const postToolUseHook = {
487
- matcher: "Bash",
488
- hooks: [{
489
- type: "command",
490
- command: `bashbros record "$TOOL_INPUT" "$TOOL_OUTPUT" ${BASHBROS_HOOK_MARKER}`
491
- }]
492
- };
493
- const sessionEndHook = {
494
- hooks: [{
495
- type: "command",
496
- command: `bashbros session-end ${BASHBROS_HOOK_MARKER}`
497
- }]
498
- };
499
- settings.hooks.PreToolUse = [
500
- ...settings.hooks.PreToolUse || [],
501
- preToolUseHook
502
- ];
503
- settings.hooks.PostToolUse = [
504
- ...settings.hooks.PostToolUse || [],
505
- postToolUseHook
506
- ];
507
- settings.hooks.SessionEnd = [
508
- ...settings.hooks.SessionEnd || [],
509
- sessionEndHook
510
- ];
511
- this.saveSettings(settings);
512
- return {
513
- success: true,
514
- message: "BashBros hooks installed successfully."
515
- };
516
- }
517
- /**
518
- * Uninstall BashBros hooks from Claude Code
519
- */
520
- static uninstall() {
521
- if (!this.isClaudeInstalled()) {
522
- return {
523
- success: false,
524
- message: "Claude Code not found."
525
- };
526
- }
527
- const settings = this.loadSettings();
528
- if (!settings.hooks) {
529
- return {
530
- success: true,
531
- message: "No hooks to uninstall."
532
- };
533
- }
534
- const filterHooks = (hooks) => {
535
- if (!hooks) return [];
536
- return hooks.filter(
537
- (h) => !h.hooks.some((hook) => hook.command.includes(BASHBROS_HOOK_MARKER))
538
- );
539
- };
540
- settings.hooks.PreToolUse = filterHooks(settings.hooks.PreToolUse);
541
- settings.hooks.PostToolUse = filterHooks(settings.hooks.PostToolUse);
542
- settings.hooks.SessionEnd = filterHooks(settings.hooks.SessionEnd);
543
- if (settings.hooks.PreToolUse?.length === 0) delete settings.hooks.PreToolUse;
544
- if (settings.hooks.PostToolUse?.length === 0) delete settings.hooks.PostToolUse;
545
- if (settings.hooks.SessionEnd?.length === 0) delete settings.hooks.SessionEnd;
546
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
547
- this.saveSettings(settings);
548
- return {
549
- success: true,
550
- message: "BashBros hooks uninstalled successfully."
551
- };
552
- }
553
- /**
554
- * Check if BashBros hooks are installed
555
- */
556
- static isInstalled(settings) {
557
- const s = settings || this.loadSettings();
558
- if (!s.hooks) return false;
559
- const hasMarker = (hooks) => {
560
- if (!hooks) return false;
561
- return hooks.some(
562
- (h) => h.hooks.some((hook) => hook.command.includes(BASHBROS_HOOK_MARKER))
563
- );
564
- };
565
- return hasMarker(s.hooks.PreToolUse) || hasMarker(s.hooks.PostToolUse) || hasMarker(s.hooks.SessionEnd);
566
- }
567
- /**
568
- * Get hook status
569
- */
570
- static getStatus() {
571
- const claudeInstalled = this.isClaudeInstalled();
572
- const settings = claudeInstalled ? this.loadSettings() : {};
573
- const hooksInstalled = this.isInstalled(settings);
574
- const allToolsInstalled = this.isAllToolsInstalled(settings);
575
- const hooks = [];
576
- if (settings.hooks?.PreToolUse) hooks.push("PreToolUse (gate)");
577
- if (settings.hooks?.PostToolUse) hooks.push("PostToolUse (record)");
578
- if (settings.hooks?.SessionEnd) hooks.push("SessionEnd (report)");
579
- if (allToolsInstalled) hooks.push("PostToolUse (all-tools)");
580
- return {
581
- claudeInstalled,
582
- hooksInstalled,
583
- allToolsInstalled,
584
- hooks
585
- };
586
- }
587
- /**
588
- * Check if all-tools recording is installed
589
- */
590
- static isAllToolsInstalled(settings) {
591
- const s = settings || this.loadSettings();
592
- if (!s.hooks?.PostToolUse) return false;
593
- return s.hooks.PostToolUse.some(
594
- (h) => h.hooks.some(
595
- (hook) => hook.command.includes(BASHBROS_ALL_TOOLS_MARKER) || hook.command.includes("bashbros-all-tools")
596
- )
597
- );
598
- }
599
- /**
600
- * Install all-tools recording hook (records ALL Claude Code tools, not just Bash)
601
- */
602
- static installAllTools() {
603
- if (!this.isClaudeInstalled()) {
604
- return {
605
- success: false,
606
- message: "Claude Code not found. Install Claude Code first."
607
- };
608
- }
609
- const settings = this.loadSettings();
610
- if (!settings.hooks) {
611
- settings.hooks = {};
612
- }
613
- if (this.isAllToolsInstalled(settings)) {
614
- return {
615
- success: true,
616
- message: "BashBros all-tools recording already installed."
617
- };
618
- }
619
- const allToolsHook = {
620
- matcher: "",
621
- // Empty matcher matches ALL tools
622
- hooks: [{
623
- type: "command",
624
- command: `bashbros record-tool ${BASHBROS_ALL_TOOLS_MARKER}`
625
- }]
626
- };
627
- settings.hooks.PostToolUse = [
628
- allToolsHook,
629
- ...settings.hooks.PostToolUse || []
630
- ];
631
- this.saveSettings(settings);
632
- return {
633
- success: true,
634
- message: "BashBros all-tools recording installed. All Claude Code tools will now be recorded."
635
- };
636
- }
637
- /**
638
- * Uninstall all-tools recording hook
639
- */
640
- static uninstallAllTools() {
641
- if (!this.isClaudeInstalled()) {
642
- return {
643
- success: false,
644
- message: "Claude Code not found."
645
- };
646
- }
647
- const settings = this.loadSettings();
648
- if (!settings.hooks?.PostToolUse) {
649
- return {
650
- success: true,
651
- message: "No all-tools hook to uninstall."
652
- };
653
- }
654
- settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
655
- (h) => !h.hooks.some(
656
- (hook) => hook.command.includes(BASHBROS_ALL_TOOLS_MARKER) || hook.command.includes("bashbros-all-tools")
657
- )
658
- );
659
- if (settings.hooks.PostToolUse.length === 0) {
660
- delete settings.hooks.PostToolUse;
661
- }
662
- if (Object.keys(settings.hooks).length === 0) {
663
- delete settings.hooks;
664
- }
665
- this.saveSettings(settings);
666
- return {
667
- success: true,
668
- message: "BashBros all-tools recording uninstalled."
669
- };
670
- }
671
- };
672
- async function gateCommand(command) {
673
- const { PolicyEngine: PolicyEngine2 } = await import("./engine-EGPAS2EX.js");
674
- const { RiskScorer } = await import("./risk-scorer-Y6KF2XCZ.js");
675
- const { loadConfig: loadConfig2 } = await import("./config-43SK6SFI.js");
676
- const config = loadConfig2();
677
- const engine = new PolicyEngine2(config);
678
- const scorer = new RiskScorer();
679
- const violations = engine.validate(command);
680
- const risk = scorer.score(command);
681
- if (violations.length > 0) {
682
- return {
683
- allowed: false,
684
- reason: violations[0].message,
685
- riskScore: risk.score
686
- };
687
- }
688
- if (risk.level === "critical") {
689
- return {
690
- allowed: false,
691
- reason: `Critical risk: ${risk.factors.join(", ")}`,
692
- riskScore: risk.score
693
- };
694
- }
695
- return {
696
- allowed: true,
697
- riskScore: risk.score
698
- };
699
- }
700
-
701
423
  // src/core.ts
702
424
  import * as pty from "node-pty";
703
425
  import { EventEmitter as EventEmitter2 } from "events";
@@ -780,32 +502,594 @@ var BashBros = class extends EventEmitter2 {
780
502
  isAllowed(command) {
781
503
  return this.policy.isAllowed(command);
782
504
  }
783
- resize(cols, rows) {
784
- if (this.ptyProcess) {
785
- this.ptyProcess.resize(cols, rows);
505
+ resize(cols, rows) {
506
+ if (this.ptyProcess) {
507
+ this.ptyProcess.resize(cols, rows);
508
+ }
509
+ }
510
+ write(data) {
511
+ if (this.ptyProcess) {
512
+ this.ptyProcess.write(data);
513
+ }
514
+ }
515
+ stop() {
516
+ if (this.ptyProcess) {
517
+ this.ptyProcess.kill();
518
+ this.ptyProcess = null;
519
+ }
520
+ }
521
+ getConfig() {
522
+ return this.config;
523
+ }
524
+ };
525
+
526
+ // src/policy/loop-detector.ts
527
+ var DEFAULT_CONFIG = {
528
+ maxRepeats: 3,
529
+ maxTurns: 100,
530
+ similarityThreshold: 0.85,
531
+ cooldownMs: 1e3,
532
+ windowSize: 20
533
+ };
534
+ var LoopDetector = class {
535
+ config;
536
+ history = [];
537
+ turnCount = 0;
538
+ constructor(config = {}) {
539
+ this.config = { ...DEFAULT_CONFIG, ...config };
540
+ }
541
+ /**
542
+ * Record a command and check for loops
543
+ */
544
+ check(command) {
545
+ const now = Date.now();
546
+ const normalized = this.normalize(command);
547
+ this.turnCount++;
548
+ if (this.turnCount >= this.config.maxTurns) {
549
+ return {
550
+ type: "max_turns",
551
+ command,
552
+ count: this.turnCount,
553
+ message: `Maximum turns reached (${this.config.maxTurns}). Session may be stuck.`
554
+ };
555
+ }
556
+ const exactMatches = this.history.filter((h) => h.command === command);
557
+ if (exactMatches.length >= this.config.maxRepeats) {
558
+ return {
559
+ type: "exact_repeat",
560
+ command,
561
+ count: exactMatches.length + 1,
562
+ message: `Command repeated ${exactMatches.length + 1} times: "${command.slice(0, 50)}..."`
563
+ };
564
+ }
565
+ const lastSame = exactMatches[exactMatches.length - 1];
566
+ if (lastSame && now - lastSame.timestamp < this.config.cooldownMs) {
567
+ return {
568
+ type: "exact_repeat",
569
+ command,
570
+ count: 2,
571
+ message: `Rapid repeat detected (${now - lastSame.timestamp}ms apart)`
572
+ };
573
+ }
574
+ const recentWindow = this.history.slice(-this.config.windowSize);
575
+ const similarCount = recentWindow.filter(
576
+ (h) => this.similarity(h.normalized, normalized) >= this.config.similarityThreshold
577
+ ).length;
578
+ if (similarCount >= this.config.maxRepeats) {
579
+ return {
580
+ type: "semantic_repeat",
581
+ command,
582
+ count: similarCount + 1,
583
+ message: `Similar commands repeated ${similarCount + 1} times`
584
+ };
585
+ }
586
+ const baseCommand = command.split(/\s+/)[0];
587
+ const toolCount = recentWindow.filter(
588
+ (h) => h.command.split(/\s+/)[0] === baseCommand
589
+ ).length;
590
+ if (toolCount >= this.config.maxRepeats * 2) {
591
+ return {
592
+ type: "tool_hammering",
593
+ command,
594
+ count: toolCount + 1,
595
+ message: `Tool "${baseCommand}" called ${toolCount + 1} times in last ${this.config.windowSize} commands`
596
+ };
597
+ }
598
+ this.history.push({ command, timestamp: now, normalized });
599
+ if (this.history.length > this.config.windowSize * 2) {
600
+ this.history = this.history.slice(-this.config.windowSize);
601
+ }
602
+ return null;
603
+ }
604
+ /**
605
+ * Normalize command for comparison
606
+ */
607
+ normalize(command) {
608
+ return command.toLowerCase().replace(/["']/g, "").replace(/\s+/g, " ").replace(/\d+/g, "N").replace(/[a-f0-9]{8,}/gi, "H").trim();
609
+ }
610
+ /**
611
+ * Calculate similarity between two strings (Jaccard index on words)
612
+ */
613
+ similarity(a, b) {
614
+ const wordsA = new Set(a.split(/\s+/));
615
+ const wordsB = new Set(b.split(/\s+/));
616
+ const intersection = new Set([...wordsA].filter((x) => wordsB.has(x)));
617
+ const union = /* @__PURE__ */ new Set([...wordsA, ...wordsB]);
618
+ if (union.size === 0) return 1;
619
+ return intersection.size / union.size;
620
+ }
621
+ /**
622
+ * Get current turn count
623
+ */
624
+ getTurnCount() {
625
+ return this.turnCount;
626
+ }
627
+ /**
628
+ * Get command frequency map
629
+ */
630
+ getFrequencyMap() {
631
+ const freq = /* @__PURE__ */ new Map();
632
+ for (const entry of this.history) {
633
+ const base = entry.command.split(/\s+/)[0];
634
+ freq.set(base, (freq.get(base) || 0) + 1);
635
+ }
636
+ return freq;
637
+ }
638
+ /**
639
+ * Reset detector state
640
+ */
641
+ reset() {
642
+ this.history = [];
643
+ this.turnCount = 0;
644
+ }
645
+ /**
646
+ * Get stats for reporting
647
+ */
648
+ getStats() {
649
+ const freq = this.getFrequencyMap();
650
+ const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
651
+ return {
652
+ turnCount: this.turnCount,
653
+ uniqueCommands: freq.size,
654
+ topCommands: sorted.slice(0, 5)
655
+ };
656
+ }
657
+ };
658
+
659
+ // src/policy/anomaly-detector.ts
660
+ var DEFAULT_CONFIG2 = {
661
+ workingHours: [6, 22],
662
+ // 6 AM to 10 PM
663
+ typicalCommandsPerMinute: 30,
664
+ knownPaths: [],
665
+ suspiciousPatterns: [],
666
+ enabled: true
667
+ };
668
+ var DEFAULT_SUSPICIOUS_PATTERNS = [
669
+ /\bpasswd\b/,
670
+ /\bshadow\b/,
671
+ /\/root\//,
672
+ /\.ssh\//,
673
+ /\.gnupg\//,
674
+ /\.aws\//,
675
+ /\.kube\//,
676
+ /wallet/i,
677
+ /crypto/i,
678
+ /bitcoin/i,
679
+ /ethereum/i,
680
+ /private.*key/i
681
+ ];
682
+ var AnomalyDetector = class {
683
+ config;
684
+ commands = [];
685
+ baselinePaths = /* @__PURE__ */ new Set();
686
+ baselineCommands = /* @__PURE__ */ new Set();
687
+ learningMode = true;
688
+ learningCount = 0;
689
+ LEARNING_THRESHOLD = 50;
690
+ constructor(config = {}) {
691
+ this.config = { ...DEFAULT_CONFIG2, ...config };
692
+ }
693
+ /**
694
+ * Check a command for anomalies
695
+ */
696
+ check(command, cwd) {
697
+ if (!this.config.enabled) return [];
698
+ const anomalies = [];
699
+ const now = Date.now();
700
+ this.commands.push({ command, timestamp: now, path: cwd });
701
+ if (this.commands.length > 1e3) {
702
+ this.commands = this.commands.slice(-500);
703
+ }
704
+ if (this.learningMode) {
705
+ this.learn(command, cwd);
706
+ if (this.learningCount >= this.LEARNING_THRESHOLD) {
707
+ this.learningMode = false;
708
+ }
709
+ return anomalies;
710
+ }
711
+ const timingAnomaly = this.checkTiming(now);
712
+ if (timingAnomaly) anomalies.push(timingAnomaly);
713
+ const freqAnomaly = this.checkFrequency(now);
714
+ if (freqAnomaly) anomalies.push(freqAnomaly);
715
+ if (cwd) {
716
+ const pathAnomaly = this.checkPath(cwd);
717
+ if (pathAnomaly) anomalies.push(pathAnomaly);
718
+ }
719
+ const patternAnomalies = this.checkPatterns(command);
720
+ anomalies.push(...patternAnomalies);
721
+ const behaviorAnomaly = this.checkBehavior(command);
722
+ if (behaviorAnomaly) anomalies.push(behaviorAnomaly);
723
+ return anomalies;
724
+ }
725
+ /**
726
+ * Learn from command (build baseline)
727
+ */
728
+ learn(command, cwd) {
729
+ this.learningCount++;
730
+ const baseCmd = command.split(/\s+/)[0];
731
+ this.baselineCommands.add(baseCmd);
732
+ if (cwd) {
733
+ this.baselinePaths.add(cwd);
734
+ const parts = cwd.split(/[/\\]/);
735
+ for (let i = 1; i <= parts.length; i++) {
736
+ this.baselinePaths.add(parts.slice(0, i).join("/"));
737
+ }
738
+ }
739
+ }
740
+ /**
741
+ * Check for timing anomalies
742
+ */
743
+ checkTiming(now) {
744
+ const hour = new Date(now).getHours();
745
+ const [start, end] = this.config.workingHours;
746
+ if (hour < start || hour >= end) {
747
+ return {
748
+ type: "timing",
749
+ severity: "info",
750
+ message: `Activity outside normal hours (${hour}:00)`,
751
+ details: { hour, workingHours: this.config.workingHours }
752
+ };
753
+ }
754
+ return null;
755
+ }
756
+ /**
757
+ * Check for frequency anomalies
758
+ */
759
+ checkFrequency(now) {
760
+ const oneMinuteAgo = now - 6e4;
761
+ const recentCommands = this.commands.filter((c) => c.timestamp > oneMinuteAgo);
762
+ const rate = recentCommands.length;
763
+ if (rate > this.config.typicalCommandsPerMinute * 2) {
764
+ return {
765
+ type: "frequency",
766
+ severity: "warning",
767
+ message: `High command rate: ${rate}/min (typical: ${this.config.typicalCommandsPerMinute})`,
768
+ details: { rate, typical: this.config.typicalCommandsPerMinute }
769
+ };
770
+ }
771
+ const fiveSecondsAgo = now - 5e3;
772
+ const burstCommands = this.commands.filter((c) => c.timestamp > fiveSecondsAgo);
773
+ if (burstCommands.length > 10) {
774
+ return {
775
+ type: "frequency",
776
+ severity: "alert",
777
+ message: `Burst detected: ${burstCommands.length} commands in 5 seconds`,
778
+ details: { count: burstCommands.length, window: "5s" }
779
+ };
780
+ }
781
+ return null;
782
+ }
783
+ /**
784
+ * Check for unusual path access
785
+ */
786
+ checkPath(path) {
787
+ if (this.config.knownPaths.length > 0) {
788
+ const isKnown = this.config.knownPaths.some(
789
+ (p) => path.startsWith(p) || path.includes(p)
790
+ );
791
+ if (!isKnown) {
792
+ return {
793
+ type: "path",
794
+ severity: "warning",
795
+ message: `Access to unexpected path: ${path}`,
796
+ details: { path, knownPaths: this.config.knownPaths }
797
+ };
798
+ }
799
+ }
800
+ if (!this.learningMode && this.baselinePaths.size > 0) {
801
+ const isBaseline = this.baselinePaths.has(path) || [...this.baselinePaths].some((p) => path.startsWith(p));
802
+ if (!isBaseline) {
803
+ return {
804
+ type: "path",
805
+ severity: "info",
806
+ message: `New path accessed: ${path}`,
807
+ details: { path, isNew: true }
808
+ };
809
+ }
810
+ }
811
+ return null;
812
+ }
813
+ /**
814
+ * Check for suspicious patterns
815
+ */
816
+ checkPatterns(command) {
817
+ const anomalies = [];
818
+ const allPatterns = [...DEFAULT_SUSPICIOUS_PATTERNS, ...this.config.suspiciousPatterns];
819
+ for (const pattern of allPatterns) {
820
+ if (pattern.test(command)) {
821
+ anomalies.push({
822
+ type: "pattern",
823
+ severity: "warning",
824
+ message: `Suspicious pattern detected: ${pattern.source}`,
825
+ details: { command: command.slice(0, 100), pattern: pattern.source }
826
+ });
827
+ }
828
+ }
829
+ return anomalies;
830
+ }
831
+ /**
832
+ * Check for behavioral anomalies
833
+ */
834
+ checkBehavior(command) {
835
+ const baseCmd = command.split(/\s+/)[0];
836
+ if (!this.learningMode && this.baselineCommands.size > 0) {
837
+ if (!this.baselineCommands.has(baseCmd)) {
838
+ const sensitiveCommands = /* @__PURE__ */ new Set([
839
+ "curl",
840
+ "wget",
841
+ "nc",
842
+ "netcat",
843
+ "ssh",
844
+ "scp",
845
+ "rsync",
846
+ "sudo",
847
+ "su",
848
+ "chmod",
849
+ "chown",
850
+ "mount",
851
+ "umount"
852
+ ]);
853
+ if (sensitiveCommands.has(baseCmd)) {
854
+ return {
855
+ type: "behavior",
856
+ severity: "warning",
857
+ message: `New sensitive command type: ${baseCmd}`,
858
+ details: { command: baseCmd, isNew: true }
859
+ };
860
+ }
861
+ }
862
+ }
863
+ return null;
864
+ }
865
+ /**
866
+ * Get anomaly stats
867
+ */
868
+ getStats() {
869
+ const now = Date.now();
870
+ const oneMinuteAgo = now - 6e4;
871
+ const recentRate = this.commands.filter((c) => c.timestamp > oneMinuteAgo).length;
872
+ return {
873
+ learningMode: this.learningMode,
874
+ learningProgress: Math.min(100, Math.round(this.learningCount / this.LEARNING_THRESHOLD * 100)),
875
+ baselineCommands: this.baselineCommands.size,
876
+ baselinePaths: this.baselinePaths.size,
877
+ recentCommandRate: recentRate
878
+ };
879
+ }
880
+ /**
881
+ * Force end learning mode
882
+ */
883
+ endLearning() {
884
+ this.learningMode = false;
885
+ }
886
+ /**
887
+ * Reset and restart learning
888
+ */
889
+ reset() {
890
+ this.commands = [];
891
+ this.baselinePaths.clear();
892
+ this.baselineCommands.clear();
893
+ this.learningMode = true;
894
+ this.learningCount = 0;
895
+ }
896
+ };
897
+
898
+ // src/policy/output-scanner.ts
899
+ var SECRET_PATTERNS = [
900
+ // API Keys
901
+ { pattern: /sk-[A-Za-z0-9]{20,}/, name: "OpenAI API Key" },
902
+ { pattern: /sk-ant-[A-Za-z0-9\-]{20,}/, name: "Anthropic API Key" },
903
+ { pattern: /ghp_[A-Za-z0-9]{36}/, name: "GitHub Token" },
904
+ { pattern: /gho_[A-Za-z0-9]{36}/, name: "GitHub OAuth Token" },
905
+ { pattern: /github_pat_[A-Za-z0-9_]{22,}/, name: "GitHub PAT" },
906
+ { pattern: /glpat-[A-Za-z0-9\-]{20,}/, name: "GitLab Token" },
907
+ { pattern: /xox[baprs]-[A-Za-z0-9\-]{10,}/, name: "Slack Token" },
908
+ { pattern: /sk_live_[A-Za-z0-9]{24,}/, name: "Stripe Secret Key" },
909
+ { pattern: /sq0atp-[A-Za-z0-9\-_]{22,}/, name: "Square Token" },
910
+ { pattern: /AKIA[A-Z0-9]{16}/, name: "AWS Access Key" },
911
+ { pattern: /amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, name: "Amazon MWS Key" },
912
+ // OAuth/JWT
913
+ { pattern: /Bearer\s+[A-Za-z0-9\-._~+/]+=*/, name: "Bearer Token" },
914
+ { pattern: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/, name: "JWT Token" },
915
+ // Credentials in output
916
+ { pattern: /password\s*[=:]\s*['"]?[^\s'"]{4,}['"]?/i, name: "Password" },
917
+ { pattern: /passwd\s*[=:]\s*['"]?[^\s'"]{4,}['"]?/i, name: "Password" },
918
+ { pattern: /api[_-]?key\s*[=:]\s*['"]?[^\s'"]{8,}['"]?/i, name: "API Key" },
919
+ { pattern: /secret\s*[=:]\s*['"]?[^\s'"]{8,}['"]?/i, name: "Secret" },
920
+ { pattern: /token\s*[=:]\s*['"]?[^\s'"]{8,}['"]?/i, name: "Token" },
921
+ // Private keys
922
+ { pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/, name: "Private Key" },
923
+ { pattern: /-----BEGIN\s+EC\s+PRIVATE\s+KEY-----/, name: "EC Private Key" },
924
+ { pattern: /-----BEGIN\s+OPENSSH\s+PRIVATE\s+KEY-----/, name: "SSH Private Key" },
925
+ { pattern: /-----BEGIN\s+PGP\s+PRIVATE\s+KEY\s+BLOCK-----/, name: "PGP Private Key" },
926
+ // Database URLs
927
+ { pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/, name: "MongoDB Connection String" },
928
+ { pattern: /postgres(ql)?:\/\/[^:]+:[^@]+@/, name: "PostgreSQL Connection String" },
929
+ { pattern: /mysql:\/\/[^:]+:[^@]+@/, name: "MySQL Connection String" },
930
+ { pattern: /redis:\/\/[^:]+:[^@]+@/, name: "Redis Connection String" },
931
+ // SSH
932
+ { pattern: /ssh-rsa\s+[A-Za-z0-9+/]+[=]{0,2}/, name: "SSH Public Key" },
933
+ { pattern: /ssh-ed25519\s+[A-Za-z0-9+/]+/, name: "SSH ED25519 Key" }
934
+ ];
935
+ var ERROR_PATTERNS = [
936
+ { pattern: /EACCES|EPERM|permission denied/i, name: "Permission Error" },
937
+ { pattern: /ENOENT|no such file|not found/i, name: "File Not Found" },
938
+ { pattern: /ECONNREFUSED|connection refused/i, name: "Connection Refused" },
939
+ { pattern: /ETIMEDOUT|timed out/i, name: "Timeout Error" },
940
+ { pattern: /segmentation fault|core dumped/i, name: "Crash" },
941
+ { pattern: /out of memory|OOM|cannot allocate/i, name: "Memory Error" },
942
+ { pattern: /stack trace|traceback|at\s+\S+:\d+:\d+/i, name: "Stack Trace" },
943
+ { pattern: /error:|fatal:|failed:/i, name: "Error Message" }
944
+ ];
945
+ var OutputScanner = class {
946
+ secretPatterns;
947
+ redactPatterns;
948
+ policy;
949
+ constructor(policy) {
950
+ this.policy = policy;
951
+ this.secretPatterns = SECRET_PATTERNS.map((p) => p.pattern);
952
+ this.redactPatterns = (policy.redactPatterns || []).map((p) => {
953
+ try {
954
+ return new RegExp(p, "gi");
955
+ } catch {
956
+ return null;
957
+ }
958
+ }).filter((p) => p !== null);
959
+ }
960
+ /**
961
+ * Scan output for secrets and sensitive data
962
+ */
963
+ scan(output) {
964
+ if (!this.policy.enabled) {
965
+ return {
966
+ hasSecrets: false,
967
+ hasErrors: false,
968
+ redactedOutput: output,
969
+ findings: []
970
+ };
971
+ }
972
+ const findings = [];
973
+ let hasSecrets = false;
974
+ let hasErrors = false;
975
+ let processedOutput = output;
976
+ if (output.length > this.policy.maxOutputLength) {
977
+ processedOutput = output.slice(0, this.policy.maxOutputLength) + "\n... [truncated]";
978
+ }
979
+ if (this.policy.scanForSecrets) {
980
+ const secretFindings = this.scanForSecrets(processedOutput);
981
+ if (secretFindings.length > 0) {
982
+ hasSecrets = true;
983
+ findings.push(...secretFindings);
984
+ }
985
+ }
986
+ if (this.policy.scanForErrors) {
987
+ const errorFindings = this.scanForErrors(processedOutput);
988
+ if (errorFindings.length > 0) {
989
+ hasErrors = true;
990
+ findings.push(...errorFindings);
991
+ }
992
+ }
993
+ const redactedOutput = this.redact(processedOutput);
994
+ return {
995
+ hasSecrets,
996
+ hasErrors,
997
+ redactedOutput,
998
+ findings
999
+ };
1000
+ }
1001
+ /**
1002
+ * Scan for secrets in output
1003
+ */
1004
+ scanForSecrets(output) {
1005
+ const findings = [];
1006
+ const lines = output.split("\n");
1007
+ for (let i = 0; i < lines.length; i++) {
1008
+ const line = lines[i];
1009
+ for (const { pattern, name } of SECRET_PATTERNS) {
1010
+ if (pattern.test(line)) {
1011
+ findings.push({
1012
+ type: "secret",
1013
+ pattern: name,
1014
+ message: `Potential ${name} found in output`,
1015
+ line: i + 1
1016
+ });
1017
+ }
1018
+ }
1019
+ }
1020
+ return findings;
1021
+ }
1022
+ /**
1023
+ * Scan for error patterns in output
1024
+ */
1025
+ scanForErrors(output) {
1026
+ const findings = [];
1027
+ const lines = output.split("\n");
1028
+ for (let i = 0; i < lines.length; i++) {
1029
+ const line = lines[i];
1030
+ for (const { pattern, name } of ERROR_PATTERNS) {
1031
+ if (pattern.test(line)) {
1032
+ findings.push({
1033
+ type: "error",
1034
+ pattern: name,
1035
+ message: `${name} detected`,
1036
+ line: i + 1
1037
+ });
1038
+ break;
1039
+ }
1040
+ }
786
1041
  }
1042
+ return findings;
787
1043
  }
788
- write(data) {
789
- if (this.ptyProcess) {
790
- this.ptyProcess.write(data);
1044
+ /**
1045
+ * Redact sensitive data from output
1046
+ */
1047
+ redact(output) {
1048
+ let redacted = output;
1049
+ for (const { pattern, name } of SECRET_PATTERNS) {
1050
+ redacted = redacted.replace(new RegExp(pattern.source, "g"), `[REDACTED ${name}]`);
791
1051
  }
1052
+ for (const pattern of this.redactPatterns) {
1053
+ redacted = redacted.replace(pattern, "[REDACTED]");
1054
+ }
1055
+ return redacted;
792
1056
  }
793
- stop() {
794
- if (this.ptyProcess) {
795
- this.ptyProcess.kill();
796
- this.ptyProcess = null;
1057
+ /**
1058
+ * Check if output contains any secrets
1059
+ */
1060
+ hasSecrets(output) {
1061
+ for (const pattern of this.secretPatterns) {
1062
+ if (pattern.test(output)) {
1063
+ return true;
1064
+ }
797
1065
  }
1066
+ return false;
798
1067
  }
799
- getConfig() {
800
- return this.config;
1068
+ /**
1069
+ * Get summary of findings
1070
+ */
1071
+ static summarize(findings) {
1072
+ if (findings.length === 0) {
1073
+ return "No issues found";
1074
+ }
1075
+ const secrets = findings.filter((f) => f.type === "secret");
1076
+ const errors = findings.filter((f) => f.type === "error");
1077
+ const parts = [];
1078
+ if (secrets.length > 0) {
1079
+ parts.push(`${secrets.length} potential secret(s)`);
1080
+ }
1081
+ if (errors.length > 0) {
1082
+ parts.push(`${errors.length} error(s)`);
1083
+ }
1084
+ return parts.join(", ");
801
1085
  }
802
1086
  };
803
1087
 
804
1088
  // src/bro/profiler.ts
805
1089
  import { execFileSync } from "child_process";
806
- import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync } from "fs";
807
- import { homedir as homedir3, platform, arch, cpus, totalmem } from "os";
808
- import { join as join3 } from "path";
1090
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "fs";
1091
+ import { homedir as homedir2, platform, arch, cpus, totalmem } from "os";
1092
+ import { join as join2 } from "path";
809
1093
  var SAFE_VERSION_COMMANDS = {
810
1094
  python: ["--version"],
811
1095
  python3: ["--version"],
@@ -838,7 +1122,7 @@ var SystemProfiler = class {
838
1122
  profile = null;
839
1123
  profilePath;
840
1124
  constructor() {
841
- this.profilePath = join3(homedir3(), ".bashbros", "system-profile.json");
1125
+ this.profilePath = join2(homedir2(), ".bashbros", "system-profile.json");
842
1126
  }
843
1127
  async scan() {
844
1128
  const profile = {
@@ -1022,8 +1306,8 @@ var SystemProfiler = class {
1022
1306
  ["composer.json", "php"]
1023
1307
  ];
1024
1308
  for (const [file, type] of checks) {
1025
- const filePath = join3(projectPath, file);
1026
- if (existsSync3(filePath)) {
1309
+ const filePath = join2(projectPath, file);
1310
+ if (existsSync2(filePath)) {
1027
1311
  try {
1028
1312
  const realPath = realpathSync(filePath);
1029
1313
  if (realPath.startsWith(realpathSync(projectPath))) {
@@ -1037,24 +1321,24 @@ var SystemProfiler = class {
1037
1321
  }
1038
1322
  detectDependencies(projectPath) {
1039
1323
  const deps = [];
1040
- const pkgPath = join3(projectPath, "package.json");
1041
- if (existsSync3(pkgPath)) {
1324
+ const pkgPath = join2(projectPath, "package.json");
1325
+ if (existsSync2(pkgPath)) {
1042
1326
  try {
1043
1327
  const realPkgPath = realpathSync(pkgPath);
1044
1328
  if (realPkgPath.startsWith(realpathSync(projectPath))) {
1045
- const pkg = JSON.parse(readFileSync3(realPkgPath, "utf-8"));
1329
+ const pkg = JSON.parse(readFileSync2(realPkgPath, "utf-8"));
1046
1330
  deps.push(...Object.keys(pkg.dependencies || {}));
1047
1331
  deps.push(...Object.keys(pkg.devDependencies || {}));
1048
1332
  }
1049
1333
  } catch {
1050
1334
  }
1051
1335
  }
1052
- const reqPath = join3(projectPath, "requirements.txt");
1053
- if (existsSync3(reqPath)) {
1336
+ const reqPath = join2(projectPath, "requirements.txt");
1337
+ if (existsSync2(reqPath)) {
1054
1338
  try {
1055
1339
  const realReqPath = realpathSync(reqPath);
1056
1340
  if (realReqPath.startsWith(realpathSync(projectPath))) {
1057
- const reqs = readFileSync3(realReqPath, "utf-8");
1341
+ const reqs = readFileSync2(realReqPath, "utf-8");
1058
1342
  const packages = reqs.split("\n").map((line) => line.split(/[=<>]/)[0].trim()).filter(Boolean);
1059
1343
  deps.push(...packages);
1060
1344
  }
@@ -1064,9 +1348,9 @@ var SystemProfiler = class {
1064
1348
  return deps.slice(0, 100);
1065
1349
  }
1066
1350
  load() {
1067
- if (existsSync3(this.profilePath)) {
1351
+ if (existsSync2(this.profilePath)) {
1068
1352
  try {
1069
- const data = readFileSync3(this.profilePath, "utf-8");
1353
+ const data = readFileSync2(this.profilePath, "utf-8");
1070
1354
  this.profile = JSON.parse(data);
1071
1355
  return this.profile;
1072
1356
  } catch {
@@ -1077,13 +1361,13 @@ var SystemProfiler = class {
1077
1361
  }
1078
1362
  save() {
1079
1363
  try {
1080
- const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync4, chmodSync } = __require("fs");
1081
- const dir = join3(homedir3(), ".bashbros");
1082
- if (!existsSync3(dir)) {
1083
- mkdirSync4(dir, { recursive: true, mode: 448 });
1364
+ const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3, chmodSync } = __require("fs");
1365
+ const dir = join2(homedir2(), ".bashbros");
1366
+ if (!existsSync2(dir)) {
1367
+ mkdirSync3(dir, { recursive: true, mode: 448 });
1084
1368
  }
1085
1369
  const filePath = this.profilePath;
1086
- writeFileSync4(filePath, JSON.stringify(this.profile, null, 2));
1370
+ writeFileSync3(filePath, JSON.stringify(this.profile, null, 2));
1087
1371
  try {
1088
1372
  chmodSync(filePath, 384);
1089
1373
  } catch {
@@ -1131,8 +1415,10 @@ var SystemProfiler = class {
1131
1415
  var TaskRouter = class {
1132
1416
  rules;
1133
1417
  profile;
1134
- constructor(profile = null) {
1418
+ ollama;
1419
+ constructor(profile = null, ollama = null) {
1135
1420
  this.profile = profile;
1421
+ this.ollama = ollama;
1136
1422
  this.rules = this.buildDefaultRules();
1137
1423
  }
1138
1424
  buildDefaultRules() {
@@ -1212,6 +1498,25 @@ var TaskRouter = class {
1212
1498
  confidence: 0.5
1213
1499
  };
1214
1500
  }
1501
+ async routeAsync(command) {
1502
+ const patternResult = this.route(command);
1503
+ if (patternResult.confidence >= 0.7) {
1504
+ return patternResult;
1505
+ }
1506
+ if (!this.ollama) {
1507
+ return patternResult;
1508
+ }
1509
+ try {
1510
+ const prompt = `Classify this command as one of: bro (simple, local task), main (complex, needs reasoning), both (can run in background). Command: "${command}". Respond with ONLY one word: bro, main, or both.`;
1511
+ const response = await this.ollama.generate(prompt, "You are a command classifier. Respond with exactly one word: bro, main, or both.");
1512
+ const decision = response.trim().toLowerCase();
1513
+ if (["bro", "main", "both"].includes(decision)) {
1514
+ return { decision, reason: "AI classification", confidence: 0.8 };
1515
+ }
1516
+ } catch {
1517
+ }
1518
+ return { decision: "main", reason: "AI fallback - defaulting to main", confidence: 0.5 };
1519
+ }
1215
1520
  looksSimple(command) {
1216
1521
  const words = command.split(/\s+/);
1217
1522
  if (words.length <= 3) return true;
@@ -1240,8 +1545,11 @@ var CommandSuggester = class {
1240
1545
  history = [];
1241
1546
  profile = null;
1242
1547
  patterns = /* @__PURE__ */ new Map();
1243
- constructor(profile = null) {
1548
+ ollama;
1549
+ aiCache = /* @__PURE__ */ new Map();
1550
+ constructor(profile = null, ollama = null) {
1244
1551
  this.profile = profile;
1552
+ this.ollama = ollama;
1245
1553
  this.initPatterns();
1246
1554
  }
1247
1555
  initPatterns() {
@@ -1274,6 +1582,34 @@ var CommandSuggester = class {
1274
1582
  const unique = this.dedupeAndRank(suggestions);
1275
1583
  return unique.slice(0, 5);
1276
1584
  }
1585
+ async suggestAsync(context) {
1586
+ const suggestions = this.suggest(context);
1587
+ if (!this.ollama) return suggestions;
1588
+ const cacheKey = JSON.stringify({ lc: context.lastCommand, lo: context.lastOutput?.slice(0, 100) });
1589
+ const cached = this.aiCache.get(cacheKey);
1590
+ if (cached && cached.expiry > Date.now()) {
1591
+ suggestions.push(...cached.suggestions);
1592
+ return this.dedupeAndRank(suggestions).slice(0, 5);
1593
+ }
1594
+ try {
1595
+ const contextStr = `Last command: ${context.lastCommand || "none"}
1596
+ Output: ${(context.lastOutput || "").slice(0, 200)}
1597
+ Project: ${context.projectType || "unknown"}`;
1598
+ const aiSuggestion = await this.ollama.suggestCommand(contextStr);
1599
+ if (aiSuggestion) {
1600
+ const aiSuggestions = [{
1601
+ command: aiSuggestion,
1602
+ description: "AI suggestion",
1603
+ confidence: 0.75,
1604
+ source: "model"
1605
+ }];
1606
+ this.aiCache.set(cacheKey, { suggestions: aiSuggestions, expiry: Date.now() + 5 * 60 * 1e3 });
1607
+ suggestions.push(...aiSuggestions);
1608
+ }
1609
+ } catch {
1610
+ }
1611
+ return this.dedupeAndRank(suggestions).slice(0, 5);
1612
+ }
1277
1613
  suggestFromPatterns(lastCommand) {
1278
1614
  const suggestions = [];
1279
1615
  for (const [key, commands] of this.patterns) {
@@ -1692,6 +2028,9 @@ var BashBro = class extends EventEmitter4 {
1692
2028
  config;
1693
2029
  ollamaAvailable = false;
1694
2030
  bashgymModelVersion = null;
2031
+ adapterRegistry;
2032
+ profileManager;
2033
+ activeProfile = null;
1695
2034
  constructor(config = {}) {
1696
2035
  super();
1697
2036
  this.config = {
@@ -1703,8 +2042,6 @@ var BashBro = class extends EventEmitter4 {
1703
2042
  ...config
1704
2043
  };
1705
2044
  this.profiler = new SystemProfiler();
1706
- this.router = new TaskRouter();
1707
- this.suggester = new CommandSuggester();
1708
2045
  this.worker = new BackgroundWorker();
1709
2046
  if (this.config.enableOllama) {
1710
2047
  this.ollama = new OllamaClient({
@@ -1712,12 +2049,19 @@ var BashBro = class extends EventEmitter4 {
1712
2049
  model: this.config.modelName
1713
2050
  });
1714
2051
  }
2052
+ this.router = new TaskRouter(null, this.ollama);
2053
+ this.suggester = new CommandSuggester(null, this.ollama);
1715
2054
  this.worker.on("complete", (data) => this.emit("task:complete", data));
1716
2055
  this.worker.on("output", (data) => this.emit("task:output", data));
1717
2056
  this.worker.on("error", (data) => this.emit("task:error", data));
1718
2057
  if (this.config.enableBashgymIntegration) {
1719
2058
  this.initBashgymIntegration();
1720
2059
  }
2060
+ this.adapterRegistry = new AdapterRegistry();
2061
+ this.profileManager = new ProfileManager();
2062
+ if (this.config.activeProfile) {
2063
+ this.activeProfile = this.profileManager.load(this.config.activeProfile);
2064
+ }
1721
2065
  }
1722
2066
  /**
1723
2067
  * Initialize bashgym integration for model hot-swap
@@ -1800,6 +2144,22 @@ var BashBro = class extends EventEmitter4 {
1800
2144
  }
1801
2145
  return this.suggester.suggest(context);
1802
2146
  }
2147
+ /**
2148
+ * AI-enhanced async routing - uses pattern matching first, falls back to Ollama
2149
+ */
2150
+ async routeAsync(command) {
2151
+ if (!this.config.enableRouting) {
2152
+ return { decision: "main", reason: "Routing disabled", confidence: 1 };
2153
+ }
2154
+ return this.router.routeAsync(command);
2155
+ }
2156
+ /**
2157
+ * AI-enhanced async suggestions - pattern matching + Ollama suggestions with caching
2158
+ */
2159
+ async suggestAsync(context) {
2160
+ if (!this.config.enableSuggestions) return [];
2161
+ return this.suggester.suggestAsync(context);
2162
+ }
1803
2163
  /**
1804
2164
  * SECURITY FIX: Safe command execution with validation
1805
2165
  */
@@ -2084,6 +2444,41 @@ var BashBro = class extends EventEmitter4 {
2084
2444
  }
2085
2445
  return false;
2086
2446
  }
2447
+ /**
2448
+ * Get model name for a specific purpose (checks active profile for adapter override)
2449
+ */
2450
+ getModelForPurpose(purpose) {
2451
+ if (!this.activeProfile) return null;
2452
+ return this.profileManager.getModelForPurpose(this.activeProfile, purpose);
2453
+ }
2454
+ /**
2455
+ * Get discovered LoRA adapters
2456
+ */
2457
+ getAdapters() {
2458
+ return this.adapterRegistry.discover();
2459
+ }
2460
+ /**
2461
+ * Get available model profiles
2462
+ */
2463
+ getProfiles() {
2464
+ return this.profileManager.list();
2465
+ }
2466
+ /**
2467
+ * Get the active model profile
2468
+ */
2469
+ getActiveProfile() {
2470
+ return this.activeProfile;
2471
+ }
2472
+ /**
2473
+ * Set the active model profile by name
2474
+ */
2475
+ setActiveProfile(name) {
2476
+ const profile = this.profileManager.load(name);
2477
+ if (!profile) return false;
2478
+ this.activeProfile = profile;
2479
+ this.emit("profile:changed", profile);
2480
+ return true;
2481
+ }
2087
2482
  // Format a nice status message
2088
2483
  status() {
2089
2484
  const lines = [
@@ -2138,151 +2533,6 @@ var BashBro = class extends EventEmitter4 {
2138
2533
  }
2139
2534
  };
2140
2535
 
2141
- // src/observability/metrics.ts
2142
- var MetricsCollector = class {
2143
- sessionId;
2144
- startTime;
2145
- commands = [];
2146
- filesModified = /* @__PURE__ */ new Set();
2147
- pathsAccessed = /* @__PURE__ */ new Set();
2148
- constructor() {
2149
- this.sessionId = this.generateSessionId();
2150
- this.startTime = /* @__PURE__ */ new Date();
2151
- }
2152
- generateSessionId() {
2153
- const now = /* @__PURE__ */ new Date();
2154
- const date = now.toISOString().slice(0, 10).replace(/-/g, "");
2155
- const time = now.toTimeString().slice(0, 8).replace(/:/g, "");
2156
- const rand = Math.random().toString(36).slice(2, 6);
2157
- return `${date}-${time}-${rand}`;
2158
- }
2159
- /**
2160
- * Record a command execution
2161
- */
2162
- record(metric) {
2163
- this.commands.push(metric);
2164
- const paths = this.extractPaths(metric.command);
2165
- for (const path of paths) {
2166
- this.pathsAccessed.add(path);
2167
- }
2168
- if (this.isWriteCommand(metric.command)) {
2169
- for (const path of paths) {
2170
- this.filesModified.add(path);
2171
- }
2172
- }
2173
- }
2174
- /**
2175
- * Get current session metrics
2176
- */
2177
- getMetrics() {
2178
- const now = /* @__PURE__ */ new Date();
2179
- const duration = now.getTime() - this.startTime.getTime();
2180
- const riskDist = { safe: 0, caution: 0, dangerous: 0, critical: 0 };
2181
- let totalRisk = 0;
2182
- for (const cmd of this.commands) {
2183
- riskDist[cmd.riskScore.level]++;
2184
- totalRisk += cmd.riskScore.score;
2185
- }
2186
- const cmdFreq = /* @__PURE__ */ new Map();
2187
- for (const cmd of this.commands) {
2188
- const base = cmd.command.split(/\s+/)[0];
2189
- cmdFreq.set(base, (cmdFreq.get(base) || 0) + 1);
2190
- }
2191
- const topCommands = [...cmdFreq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
2192
- const violationsByType = {};
2193
- for (const cmd of this.commands) {
2194
- for (const v of cmd.violations) {
2195
- violationsByType[v.type] = (violationsByType[v.type] || 0) + 1;
2196
- }
2197
- }
2198
- const totalExecTime = this.commands.reduce((sum, c) => sum + c.duration, 0);
2199
- const avgExecTime = this.commands.length > 0 ? totalExecTime / this.commands.length : 0;
2200
- return {
2201
- sessionId: this.sessionId,
2202
- startTime: this.startTime,
2203
- duration,
2204
- commandCount: this.commands.length,
2205
- blockedCount: this.commands.filter((c) => !c.allowed).length,
2206
- uniqueCommands: cmdFreq.size,
2207
- topCommands,
2208
- riskDistribution: riskDist,
2209
- avgRiskScore: this.commands.length > 0 ? totalRisk / this.commands.length : 0,
2210
- avgExecutionTime: avgExecTime,
2211
- totalExecutionTime: totalExecTime,
2212
- filesModified: [...this.filesModified],
2213
- pathsAccessed: [...this.pathsAccessed],
2214
- violationsByType
2215
- };
2216
- }
2217
- /**
2218
- * Extract paths from a command
2219
- */
2220
- extractPaths(command) {
2221
- const paths = [];
2222
- const tokens = command.split(/\s+/);
2223
- for (const token of tokens) {
2224
- if (token.startsWith("-")) continue;
2225
- if (token.startsWith("/") || token.startsWith("./") || token.startsWith("../") || token.startsWith("~/") || token.includes(".")) {
2226
- paths.push(token);
2227
- }
2228
- }
2229
- return paths;
2230
- }
2231
- /**
2232
- * Check if command modifies files
2233
- */
2234
- isWriteCommand(command) {
2235
- const writePatterns = [
2236
- /^(vim|vi|nano|emacs|code)\s/,
2237
- /^(touch|mkdir|cp|mv|rm)\s/,
2238
- /^(echo|cat|printf).*>/,
2239
- /^(git\s+(add|commit|checkout|reset))/,
2240
- /^(npm|yarn|pnpm)\s+(install|uninstall)/,
2241
- /^(pip|pip3)\s+(install|uninstall)/,
2242
- /^chmod\s/,
2243
- /^chown\s/
2244
- ];
2245
- return writePatterns.some((p) => p.test(command));
2246
- }
2247
- /**
2248
- * Get recent commands
2249
- */
2250
- getRecentCommands(n = 10) {
2251
- return this.commands.slice(-n);
2252
- }
2253
- /**
2254
- * Get blocked commands
2255
- */
2256
- getBlockedCommands() {
2257
- return this.commands.filter((c) => !c.allowed);
2258
- }
2259
- /**
2260
- * Get high-risk commands
2261
- */
2262
- getHighRiskCommands(threshold = 6) {
2263
- return this.commands.filter((c) => c.riskScore.score >= threshold);
2264
- }
2265
- /**
2266
- * Format duration for display
2267
- */
2268
- static formatDuration(ms) {
2269
- if (ms < 1e3) return `${ms}ms`;
2270
- if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2271
- if (ms < 36e5) return `${Math.floor(ms / 6e4)}m ${Math.floor(ms % 6e4 / 1e3)}s`;
2272
- return `${Math.floor(ms / 36e5)}h ${Math.floor(ms % 36e5 / 6e4)}m`;
2273
- }
2274
- /**
2275
- * Reset collector
2276
- */
2277
- reset() {
2278
- this.sessionId = this.generateSessionId();
2279
- this.startTime = /* @__PURE__ */ new Date();
2280
- this.commands = [];
2281
- this.filesModified.clear();
2282
- this.pathsAccessed.clear();
2283
- }
2284
- };
2285
-
2286
2536
  // src/observability/cost.ts
2287
2537
  var MODEL_PRICING = {
2288
2538
  "claude-opus-4": { inputPer1k: 0.015, outputPer1k: 0.075 },
@@ -2627,15 +2877,15 @@ var ReportGenerator = class {
2627
2877
  };
2628
2878
 
2629
2879
  // src/safety/undo-stack.ts
2630
- import { existsSync as existsSync4, unlinkSync, mkdirSync as mkdirSync3, copyFileSync, readdirSync, statSync } from "fs";
2631
- import { join as join4, dirname } from "path";
2632
- import { homedir as homedir4 } from "os";
2633
- var DEFAULT_CONFIG = {
2880
+ import { existsSync as existsSync3, unlinkSync, mkdirSync as mkdirSync2, copyFileSync, readdirSync, statSync } from "fs";
2881
+ import { join as join3, dirname } from "path";
2882
+ import { homedir as homedir3 } from "os";
2883
+ var DEFAULT_CONFIG3 = {
2634
2884
  maxStackSize: 100,
2635
2885
  maxFileSize: 10 * 1024 * 1024,
2636
2886
  // 10MB
2637
2887
  ttlMinutes: 60,
2638
- backupPath: join4(homedir4(), ".bashbros", "undo"),
2888
+ backupPath: join3(homedir3(), ".bashbros", "undo"),
2639
2889
  enabled: true
2640
2890
  };
2641
2891
  var UndoStack = class {
@@ -2644,13 +2894,13 @@ var UndoStack = class {
2644
2894
  config;
2645
2895
  undoDir;
2646
2896
  constructor(policy) {
2647
- this.config = { ...DEFAULT_CONFIG };
2897
+ this.config = { ...DEFAULT_CONFIG3 };
2648
2898
  if (policy) {
2649
2899
  if (typeof policy.maxStackSize === "number") this.config.maxStackSize = policy.maxStackSize;
2650
2900
  if (typeof policy.maxFileSize === "number") this.config.maxFileSize = policy.maxFileSize;
2651
2901
  if (typeof policy.ttlMinutes === "number") this.config.ttlMinutes = policy.ttlMinutes;
2652
2902
  if (typeof policy.backupPath === "string") {
2653
- this.config.backupPath = policy.backupPath.replace("~", homedir4());
2903
+ this.config.backupPath = policy.backupPath.replace("~", homedir3());
2654
2904
  }
2655
2905
  if (typeof policy.enabled === "boolean") this.config.enabled = policy.enabled;
2656
2906
  }
@@ -2660,8 +2910,8 @@ var UndoStack = class {
2660
2910
  this.cleanupOldBackups();
2661
2911
  }
2662
2912
  ensureUndoDir() {
2663
- if (!existsSync4(this.undoDir)) {
2664
- mkdirSync3(this.undoDir, { recursive: true, mode: 448 });
2913
+ if (!existsSync3(this.undoDir)) {
2914
+ mkdirSync2(this.undoDir, { recursive: true, mode: 448 });
2665
2915
  }
2666
2916
  }
2667
2917
  /**
@@ -2675,7 +2925,7 @@ var UndoStack = class {
2675
2925
  const files = readdirSync(this.undoDir);
2676
2926
  for (const file of files) {
2677
2927
  if (!file.endsWith(".backup")) continue;
2678
- const filePath = join4(this.undoDir, file);
2928
+ const filePath = join3(this.undoDir, file);
2679
2929
  try {
2680
2930
  const stats = statSync(filePath);
2681
2931
  if (stats.mtimeMs < cutoff) {
@@ -2716,7 +2966,7 @@ var UndoStack = class {
2716
2966
  * Record a file modification (backs up original)
2717
2967
  */
2718
2968
  recordModify(path, command) {
2719
- if (!this.config.enabled || !existsSync4(path)) {
2969
+ if (!this.config.enabled || !existsSync3(path)) {
2720
2970
  return null;
2721
2971
  }
2722
2972
  const stats = statSync(path);
@@ -2732,7 +2982,7 @@ var UndoStack = class {
2732
2982
  return entry;
2733
2983
  }
2734
2984
  const id = this.generateId();
2735
- const backupPath = join4(this.undoDir, `${id}.backup`);
2985
+ const backupPath = join3(this.undoDir, `${id}.backup`);
2736
2986
  try {
2737
2987
  copyFileSync(path, backupPath);
2738
2988
  const entry = {
@@ -2753,7 +3003,7 @@ var UndoStack = class {
2753
3003
  * Record a file deletion (backs up content)
2754
3004
  */
2755
3005
  recordDelete(path, command) {
2756
- if (!this.config.enabled || !existsSync4(path)) {
3006
+ if (!this.config.enabled || !existsSync3(path)) {
2757
3007
  return null;
2758
3008
  }
2759
3009
  const stats = statSync(path);
@@ -2769,7 +3019,7 @@ var UndoStack = class {
2769
3019
  return entry;
2770
3020
  }
2771
3021
  const id = this.generateId();
2772
- const backupPath = join4(this.undoDir, `${id}.backup`);
3022
+ const backupPath = join3(this.undoDir, `${id}.backup`);
2773
3023
  try {
2774
3024
  copyFileSync(path, backupPath);
2775
3025
  const entry = {
@@ -2796,11 +3046,11 @@ var UndoStack = class {
2796
3046
  const isModify = /^(sed|awk|vim|vi|nano|code)\s/.test(command) || /^(echo|cat).*>>/.test(command);
2797
3047
  for (const path of paths) {
2798
3048
  let entry = null;
2799
- if (isDelete && existsSync4(path)) {
3049
+ if (isDelete && existsSync3(path)) {
2800
3050
  entry = this.recordDelete(path, command);
2801
- } else if (isModify && existsSync4(path)) {
3051
+ } else if (isModify && existsSync3(path)) {
2802
3052
  entry = this.recordModify(path, command);
2803
- } else if (isCreate && !existsSync4(path)) {
3053
+ } else if (isCreate && !existsSync3(path)) {
2804
3054
  entry = this.recordCreate(path, command);
2805
3055
  }
2806
3056
  if (entry) {
@@ -2826,7 +3076,7 @@ var UndoStack = class {
2826
3076
  try {
2827
3077
  switch (entry.operation) {
2828
3078
  case "create":
2829
- if (existsSync4(entry.path)) {
3079
+ if (existsSync3(entry.path)) {
2830
3080
  unlinkSync(entry.path);
2831
3081
  return {
2832
3082
  success: true,
@@ -2840,7 +3090,7 @@ var UndoStack = class {
2840
3090
  entry
2841
3091
  };
2842
3092
  case "modify":
2843
- if (entry.backupPath && existsSync4(entry.backupPath)) {
3093
+ if (entry.backupPath && existsSync3(entry.backupPath)) {
2844
3094
  copyFileSync(entry.backupPath, entry.path);
2845
3095
  return {
2846
3096
  success: true,
@@ -2854,10 +3104,10 @@ var UndoStack = class {
2854
3104
  entry
2855
3105
  };
2856
3106
  case "delete":
2857
- if (entry.backupPath && existsSync4(entry.backupPath)) {
3107
+ if (entry.backupPath && existsSync3(entry.backupPath)) {
2858
3108
  const dir = dirname(entry.path);
2859
- if (!existsSync4(dir)) {
2860
- mkdirSync3(dir, { recursive: true });
3109
+ if (!existsSync3(dir)) {
3110
+ mkdirSync2(dir, { recursive: true });
2861
3111
  }
2862
3112
  copyFileSync(entry.backupPath, entry.path);
2863
3113
  return {
@@ -2913,7 +3163,7 @@ var UndoStack = class {
2913
3163
  */
2914
3164
  clear() {
2915
3165
  for (const entry of this.stack) {
2916
- if (entry.backupPath && existsSync4(entry.backupPath)) {
3166
+ if (entry.backupPath && existsSync3(entry.backupPath)) {
2917
3167
  try {
2918
3168
  unlinkSync(entry.backupPath);
2919
3169
  } catch {
@@ -2929,7 +3179,7 @@ var UndoStack = class {
2929
3179
  this.stack.push(entry);
2930
3180
  if (this.stack.length > this.config.maxStackSize) {
2931
3181
  const removed = this.stack.shift();
2932
- if (removed?.backupPath && existsSync4(removed.backupPath)) {
3182
+ if (removed?.backupPath && existsSync3(removed.backupPath)) {
2933
3183
  try {
2934
3184
  unlinkSync(removed.backupPath);
2935
3185
  } catch {
@@ -2959,155 +3209,21 @@ var UndoStack = class {
2959
3209
  }
2960
3210
  };
2961
3211
 
2962
- // src/policy/loop-detector.ts
2963
- var DEFAULT_CONFIG2 = {
2964
- maxRepeats: 3,
2965
- maxTurns: 100,
2966
- similarityThreshold: 0.85,
2967
- cooldownMs: 1e3,
2968
- windowSize: 20
2969
- };
2970
- var LoopDetector = class {
2971
- config;
2972
- history = [];
2973
- turnCount = 0;
2974
- constructor(config = {}) {
2975
- this.config = { ...DEFAULT_CONFIG2, ...config };
2976
- }
2977
- /**
2978
- * Record a command and check for loops
2979
- */
2980
- check(command) {
2981
- const now = Date.now();
2982
- const normalized = this.normalize(command);
2983
- this.turnCount++;
2984
- if (this.turnCount >= this.config.maxTurns) {
2985
- return {
2986
- type: "max_turns",
2987
- command,
2988
- count: this.turnCount,
2989
- message: `Maximum turns reached (${this.config.maxTurns}). Session may be stuck.`
2990
- };
2991
- }
2992
- const exactMatches = this.history.filter((h) => h.command === command);
2993
- if (exactMatches.length >= this.config.maxRepeats) {
2994
- return {
2995
- type: "exact_repeat",
2996
- command,
2997
- count: exactMatches.length + 1,
2998
- message: `Command repeated ${exactMatches.length + 1} times: "${command.slice(0, 50)}..."`
2999
- };
3000
- }
3001
- const lastSame = exactMatches[exactMatches.length - 1];
3002
- if (lastSame && now - lastSame.timestamp < this.config.cooldownMs) {
3003
- return {
3004
- type: "exact_repeat",
3005
- command,
3006
- count: 2,
3007
- message: `Rapid repeat detected (${now - lastSame.timestamp}ms apart)`
3008
- };
3009
- }
3010
- const recentWindow = this.history.slice(-this.config.windowSize);
3011
- const similarCount = recentWindow.filter(
3012
- (h) => this.similarity(h.normalized, normalized) >= this.config.similarityThreshold
3013
- ).length;
3014
- if (similarCount >= this.config.maxRepeats) {
3015
- return {
3016
- type: "semantic_repeat",
3017
- command,
3018
- count: similarCount + 1,
3019
- message: `Similar commands repeated ${similarCount + 1} times`
3020
- };
3021
- }
3022
- const baseCommand = command.split(/\s+/)[0];
3023
- const toolCount = recentWindow.filter(
3024
- (h) => h.command.split(/\s+/)[0] === baseCommand
3025
- ).length;
3026
- if (toolCount >= this.config.maxRepeats * 2) {
3027
- return {
3028
- type: "tool_hammering",
3029
- command,
3030
- count: toolCount + 1,
3031
- message: `Tool "${baseCommand}" called ${toolCount + 1} times in last ${this.config.windowSize} commands`
3032
- };
3033
- }
3034
- this.history.push({ command, timestamp: now, normalized });
3035
- if (this.history.length > this.config.windowSize * 2) {
3036
- this.history = this.history.slice(-this.config.windowSize);
3037
- }
3038
- return null;
3039
- }
3040
- /**
3041
- * Normalize command for comparison
3042
- */
3043
- normalize(command) {
3044
- return command.toLowerCase().replace(/["']/g, "").replace(/\s+/g, " ").replace(/\d+/g, "N").replace(/[a-f0-9]{8,}/gi, "H").trim();
3045
- }
3046
- /**
3047
- * Calculate similarity between two strings (Jaccard index on words)
3048
- */
3049
- similarity(a, b) {
3050
- const wordsA = new Set(a.split(/\s+/));
3051
- const wordsB = new Set(b.split(/\s+/));
3052
- const intersection = new Set([...wordsA].filter((x) => wordsB.has(x)));
3053
- const union = /* @__PURE__ */ new Set([...wordsA, ...wordsB]);
3054
- if (union.size === 0) return 1;
3055
- return intersection.size / union.size;
3056
- }
3057
- /**
3058
- * Get current turn count
3059
- */
3060
- getTurnCount() {
3061
- return this.turnCount;
3062
- }
3063
- /**
3064
- * Get command frequency map
3065
- */
3066
- getFrequencyMap() {
3067
- const freq = /* @__PURE__ */ new Map();
3068
- for (const entry of this.history) {
3069
- const base = entry.command.split(/\s+/)[0];
3070
- freq.set(base, (freq.get(base) || 0) + 1);
3071
- }
3072
- return freq;
3073
- }
3074
- /**
3075
- * Reset detector state
3076
- */
3077
- reset() {
3078
- this.history = [];
3079
- this.turnCount = 0;
3080
- }
3081
- /**
3082
- * Get stats for reporting
3083
- */
3084
- getStats() {
3085
- const freq = this.getFrequencyMap();
3086
- const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]);
3087
- return {
3088
- turnCount: this.turnCount,
3089
- uniqueCommands: freq.size,
3090
- topCommands: sorted.slice(0, 5)
3091
- };
3092
- }
3093
- };
3094
-
3095
3212
  export {
3096
3213
  BashgymIntegration,
3097
3214
  getBashgymIntegration,
3098
3215
  resetBashgymIntegration,
3099
- ClaudeCodeHooks,
3100
- gateCommand,
3101
3216
  BashBros,
3217
+ LoopDetector,
3218
+ AnomalyDetector,
3219
+ OutputScanner,
3102
3220
  SystemProfiler,
3103
3221
  TaskRouter,
3104
3222
  CommandSuggester,
3105
3223
  BackgroundWorker,
3106
3224
  BashBro,
3107
- MetricsCollector,
3108
3225
  CostEstimator,
3109
3226
  ReportGenerator,
3110
- UndoStack,
3111
- LoopDetector
3227
+ UndoStack
3112
3228
  };
3113
- //# sourceMappingURL=chunk-2RPTM6EQ.js.map
3229
+ //# sourceMappingURL=chunk-7OEWYFN3.js.map