panopticon-cli 1.0.0__tar.gz → 1.1.0__tar.gz

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 (22) hide show
  1. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/LICENSE +21 -21
  2. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/PKG-INFO +1 -3
  3. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/README.md +42 -44
  4. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/__init__.py +11 -11
  5. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/cli.py +197 -164
  6. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/memory.py +60 -56
  7. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/observer.py +74 -64
  8. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/policies.py +68 -53
  9. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon/sentinel.py +143 -122
  10. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/PKG-INFO +1 -3
  11. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/pyproject.toml +43 -43
  12. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/tests/test_cli.py +15 -14
  13. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/tests/test_memory.py +38 -29
  14. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/tests/test_observer.py +37 -34
  15. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/tests/test_policies.py +47 -35
  16. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/tests/test_sentinel.py +33 -32
  17. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/SOURCES.txt +0 -0
  18. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/dependency_links.txt +0 -0
  19. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/entry_points.txt +0 -0
  20. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/requires.txt +0 -0
  21. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/panopticon_cli.egg-info/top_level.txt +0 -0
  22. {panopticon_cli-1.0.0 → panopticon_cli-1.1.0}/setup.cfg +0 -0
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Ak
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: panopticon-cli
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: The Cognitive Immune System for Autonomous CLI Agents.
5
5
  Author: Ak
6
6
  License: MIT
@@ -33,10 +33,8 @@ Dynamic: license-file
33
33
  > **The Cognitive Immune System (and glorified babysitter) for Autonomous CLI Agents.**
34
34
 
35
35
  [![PyPI version](https://badge.fury.io/py/panopticon-cli.svg)](https://pypi.org/project/panopticon-cli/)
36
- [![PyPI Downloads](https://static.pepy.tech/badge/panopticon-cli)](https://pypi.org/project/panopticon-cli/)
37
36
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
37
  [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
39
- [![CI](https://github.com/ak/panopticon/actions/workflows/ci.yml/badge.svg)](https://github.com/ak/panopticon/actions)
40
38
 
41
39
  Autonomous CLI agents (like Claude Code, AutoGPT, and Antigravity) are amazing... right until they hallucinate, burn through $50 of your API credits, or confidently try to `rm -rf /` your hard drive.
42
40
 
@@ -1,44 +1,42 @@
1
- # PANOPTICON 👁️
2
-
3
- > **The Cognitive Immune System (and glorified babysitter) for Autonomous CLI Agents.**
4
-
5
- [![PyPI version](https://badge.fury.io/py/panopticon-cli.svg)](https://pypi.org/project/panopticon-cli/)
6
- [![PyPI Downloads](https://static.pepy.tech/badge/panopticon-cli)](https://pypi.org/project/panopticon-cli/)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
- [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
9
- [![CI](https://github.com/ak/panopticon/actions/workflows/ci.yml/badge.svg)](https://github.com/ak/panopticon/actions)
10
-
11
- Autonomous CLI agents (like Claude Code, AutoGPT, and Antigravity) are amazing... right until they hallucinate, burn through $50 of your API credits, or confidently try to `rm -rf /` your hard drive.
12
-
13
- **Panopticon** is a zero-latency, non-blocking wrapper that silently watches your agent's terminal output. When the AI inevitably tries to do something incredibly stupid, Panopticon's **State Guillotine** drops.
14
-
15
- It intercepts the rogue process, uses a Meta-Agent to figure out why your AI is crying, and **forcefully injects the correction directly into the agent's live `stdin` stream** like a disappointed senior developer taking over the keyboard. Oh, and it saves that failure to a SQLite database so the agent never makes the exact same mistake twice.
16
-
17
- ## Why you need this
18
-
19
- - **Native TTY Wrapping:** Slap it in front of any CLI. Loading spinners, ANSI colors, and interactive prompts still work perfectly. We just spy on them natively.
20
- - **The Policy Cascade (Iron Dome):**
21
- - **Level 1 (Blacklist):** Instant, zero-cost kills for destructive actions. Because your AI *will* try to drop your production database eventually.
22
- - **Level 2 (Fuzzy Heuristics):** Zero-cost math thresholds that catch repetitive loops. Stops the agent from running `cat missing_file.py` 300 times in a row.
23
- - **Level 3 (Universal Semantic Logic):** Streams a sliding-window of the terminal to a Meta-Agent (Claude, GPT, or Gemini) to judge your sub-agent's poor life choices.
24
- - **True Live Injection:** We don't just kill the agent and leave you hanging. Panopticon literally types the course-correction prompt into the interactive terminal for you, saving the session.
25
- - **Persistent Semantic Memory:** AI agents have goldfish memory. We use SQLite and Jaccard keyword routing to permanently scar them with their past failures so they actually learn.
26
-
27
- ## Quick Start
28
-
29
- ```bash
30
- # Install globally from PyPI
31
- pip install panopticon-cli
32
-
33
- # Export your preferred API Key (Panopticon dynamically routes to whatever you actually pay for)
34
- export OPENAI_API_KEY="sk-..."
35
- # OR export ANTHROPIC_API_KEY="..."
36
- # OR export GEMINI_API_KEY="..."
37
-
38
- # Slap it in front of your agent!
39
- panopticon claude
40
- # OR
41
- panopticon agy
42
- ```
43
-
44
- See [USAGE.md](USAGE.md) for deeper configuration so you can start dropping the Guillotine.
1
+ # PANOPTICON 👁️
2
+
3
+ > **The Cognitive Immune System (and glorified babysitter) for Autonomous CLI Agents.**
4
+
5
+ [![PyPI version](https://badge.fury.io/py/panopticon-cli.svg)](https://pypi.org/project/panopticon-cli/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
8
+
9
+ Autonomous CLI agents (like Claude Code, AutoGPT, and Antigravity) are amazing... right until they hallucinate, burn through $50 of your API credits, or confidently try to `rm -rf /` your hard drive.
10
+
11
+ **Panopticon** is a zero-latency, non-blocking wrapper that silently watches your agent's terminal output. When the AI inevitably tries to do something incredibly stupid, Panopticon's **State Guillotine** drops.
12
+
13
+ It intercepts the rogue process, uses a Meta-Agent to figure out why your AI is crying, and **forcefully injects the correction directly into the agent's live `stdin` stream** like a disappointed senior developer taking over the keyboard. Oh, and it saves that failure to a SQLite database so the agent never makes the exact same mistake twice.
14
+
15
+ ## Why you need this
16
+
17
+ - **Native TTY Wrapping:** Slap it in front of any CLI. Loading spinners, ANSI colors, and interactive prompts still work perfectly. We just spy on them natively.
18
+ - **The Policy Cascade (Iron Dome):**
19
+ - **Level 1 (Blacklist):** Instant, zero-cost kills for destructive actions. Because your AI *will* try to drop your production database eventually.
20
+ - **Level 2 (Fuzzy Heuristics):** Zero-cost math thresholds that catch repetitive loops. Stops the agent from running `cat missing_file.py` 300 times in a row.
21
+ - **Level 3 (Universal Semantic Logic):** Streams a sliding-window of the terminal to a Meta-Agent (Claude, GPT, or Gemini) to judge your sub-agent's poor life choices.
22
+ - **True Live Injection:** We don't just kill the agent and leave you hanging. Panopticon literally types the course-correction prompt into the interactive terminal for you, saving the session.
23
+ - **Persistent Semantic Memory:** AI agents have goldfish memory. We use SQLite and Jaccard keyword routing to permanently scar them with their past failures so they actually learn.
24
+
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ # Install globally from PyPI
29
+ pip install panopticon-cli
30
+
31
+ # Export your preferred API Key (Panopticon dynamically routes to whatever you actually pay for)
32
+ export OPENAI_API_KEY="sk-..."
33
+ # OR export ANTHROPIC_API_KEY="..."
34
+ # OR export GEMINI_API_KEY="..."
35
+
36
+ # Slap it in front of your agent!
37
+ panopticon claude
38
+ # OR
39
+ panopticon agy
40
+ ```
41
+
42
+ See [USAGE.md](USAGE.md) for deeper configuration so you can start dropping the Guillotine.
@@ -1,11 +1,11 @@
1
- from .observer import PanopticonObserver
2
- from .policies import AdversarialLogicCheck, AntiLoopPolicy, BlacklistPolicy
3
- from .memory import PersistentMemory
4
-
5
- __all__ = [
6
- "PanopticonObserver",
7
- "AdversarialLogicCheck",
8
- "AntiLoopPolicy",
9
- "BlacklistPolicy",
10
- "PersistentMemory"
11
- ]
1
+ from .observer import PanopticonObserver
2
+ from .policies import AdversarialLogicCheck, AntiLoopPolicy, BlacklistPolicy
3
+ from .memory import PersistentMemory
4
+
5
+ __all__ = [
6
+ "PanopticonObserver",
7
+ "AdversarialLogicCheck",
8
+ "AntiLoopPolicy",
9
+ "BlacklistPolicy",
10
+ "PersistentMemory",
11
+ ]
@@ -1,164 +1,197 @@
1
- import subprocess
2
- import threading
3
- import sys
4
- import time
5
- import argparse
6
- import re
7
- import os
8
- import signal
9
- import codecs
10
- from typing import List
11
- from .observer import PanopticonObserver, InterventionException
12
- from .policies import AdversarialLogicCheck, AntiLoopPolicy, BlacklistPolicy
13
-
14
- def strip_ansi(text: str) -> str:
15
- ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
16
- return ansi_escape.sub('', text)
17
-
18
- class CLIWrapper:
19
- def __init__(self, command: List[str]):
20
- self.command = command
21
- target_agent = self.command[0] if self.command else "unknown"
22
-
23
- self.observer = PanopticonObserver(policies=[
24
- BlacklistPolicy(forbidden_patterns=["rm -rf /", "DROP TABLE"]),
25
- AntiLoopPolicy(window=3, threshold=0.85),
26
- AdversarialLogicCheck(target_agent=target_agent)
27
- ])
28
-
29
- self.process = None
30
- self.buffer = []
31
- self.running = False
32
- self.lock = threading.Lock()
33
-
34
- def run(self):
35
- self.running = True
36
- env = os.environ.copy()
37
- env["PYTHONUNBUFFERED"] = "1"
38
- env["FORCE_COLOR"] = "1"
39
- env["CLICOLOR_FORCE"] = "1"
40
-
41
- print(f"[PANOPTICON] Booting Immune System for: {' '.join(self.command)}")
42
- print(f"[PANOPTICON] Policies Active: Blacklist, AntiLoop (Fuzzy), AdversarialLogic")
43
-
44
- # Flaw 2 Fix: Removing text=True to read raw binary bytes
45
- self.process = subprocess.Popen(
46
- self.command,
47
- stdout=subprocess.PIPE,
48
- stderr=subprocess.STDOUT,
49
- stdin=subprocess.PIPE,
50
- bufsize=0,
51
- env=env
52
- )
53
-
54
- t_out = threading.Thread(target=self._read_stdout)
55
- t_out.daemon = True
56
- t_out.start()
57
-
58
- t_in = threading.Thread(target=self._forward_stdin)
59
- t_in.daemon = True
60
- t_in.start()
61
-
62
- t_eval = threading.Thread(target=self._eval_loop)
63
- t_eval.daemon = True
64
- t_eval.start()
65
-
66
- self.process.wait()
67
- self.running = False
68
-
69
- def _forward_stdin(self):
70
- try:
71
- while self.running and self.process.poll() is None:
72
- # Read binary from stdin to pass natively to process
73
- line = sys.stdin.buffer.readline()
74
- if line:
75
- self.process.stdin.write(line)
76
- self.process.stdin.flush()
77
- except Exception:
78
- pass
79
-
80
- def _read_stdout(self):
81
- # Flaw 2 Fix: Safely decode multi-byte UTF-8 emojis incrementally
82
- decoder = codecs.getincrementaldecoder('utf-8')(errors='replace')
83
- try:
84
- while self.running and self.process.poll() is None:
85
- byte_chunk = self.process.stdout.read(1)
86
- if not byte_chunk:
87
- break
88
-
89
- # Mirror raw bytes to actual terminal seamlessly
90
- sys.stdout.buffer.write(byte_chunk)
91
- sys.stdout.buffer.flush()
92
-
93
- char_str = decoder.decode(byte_chunk)
94
- if char_str:
95
- with self.lock:
96
- self.buffer.append(char_str)
97
- except Exception:
98
- pass
99
-
100
- def _eval_loop(self):
101
- while self.running and self.process.poll() is None:
102
- time.sleep(15)
103
-
104
- with self.lock:
105
- if not self.buffer:
106
- continue
107
- full_text = "".join(self.buffer)
108
-
109
- # Flaw 1 Fix: Sliding window overlap to prevent bridging blindness
110
- overlap = full_text[-100:] if len(full_text) > 100 else ""
111
- self.buffer.clear()
112
- if overlap:
113
- self.buffer.append(overlap)
114
-
115
- clean_text = strip_ansi(full_text)
116
- self.observer.log_action("CLI_Agent", clean_text, "cli_execution", len(clean_text) // 4)
117
-
118
- try:
119
- self.observer._evaluate_state("CLI_Agent")
120
- except InterventionException as e:
121
- print(f"\n\n[PANOPTICON GUILLOTINE TRIGGERED]")
122
- print(f"[REASON]: {e.args[0]}")
123
- print(f"[LIVE INJECTION]: Interrupting agent and injecting correction...\n")
124
-
125
- # Flaw 1 Fix: The "Unkillable Zombie" SIGINT Interrupt
126
- try:
127
- if sys.platform != "win32":
128
- self.process.send_signal(signal.SIGINT)
129
- # Give the agent a split second to catch the interrupt before writing to stdin
130
- time.sleep(0.5)
131
- except Exception:
132
- pass
133
-
134
- try:
135
- # Write the injection payload as binary
136
- payload = (e.course_correction + "\n").encode('utf-8')
137
- self.process.stdin.write(payload)
138
- self.process.stdin.flush()
139
- except Exception as ex:
140
- print(f"[ERROR] Live injection failed or agent deadlocked: {ex}. Hard terminating.")
141
- self.process.terminate()
142
- self.running = False
143
- break
144
-
145
- def main():
146
- try:
147
- from dotenv import load_dotenv
148
- load_dotenv()
149
- except ImportError:
150
- pass
151
-
152
- parser = argparse.ArgumentParser(description="Panopticon: Production-Grade Immune System for AI CLIs")
153
- parser.add_argument("command", nargs=argparse.REMAINDER, help="The command to run, e.g., 'claude'")
154
- args = parser.parse_args()
155
-
156
- if not args.command:
157
- print("Usage: panopticon [command]")
158
- sys.exit(1)
159
-
160
- wrapper = CLIWrapper(args.command)
161
- wrapper.run()
162
-
163
- if __name__ == "__main__":
164
- main()
1
+ import subprocess
2
+ import threading
3
+ import sys
4
+ import time
5
+ import argparse
6
+ import re
7
+ import os
8
+ import signal
9
+ import codecs
10
+ from typing import List
11
+ from .observer import PanopticonObserver, InterventionException
12
+ from .policies import AdversarialLogicCheck, AntiLoopPolicy, BlacklistPolicy
13
+
14
+
15
+ def strip_ansi(text: str) -> str:
16
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
17
+ return ansi_escape.sub("", text)
18
+
19
+
20
+ class CLIWrapper:
21
+ def __init__(self, command: List[str]):
22
+ self.command = command
23
+ target_agent = self.command[0] if self.command else "unknown"
24
+
25
+ self.observer = PanopticonObserver(
26
+ policies=[
27
+ BlacklistPolicy(forbidden_patterns=["rm -rf /", "DROP TABLE"]),
28
+ AntiLoopPolicy(window=3, threshold=0.85),
29
+ AdversarialLogicCheck(target_agent=target_agent),
30
+ ]
31
+ )
32
+
33
+ self.process = None
34
+ self.buffer = []
35
+ self.running = False
36
+ self.lock = threading.Lock()
37
+
38
+ def run(self):
39
+ self.running = True
40
+ env = os.environ.copy()
41
+ env["PYTHONUNBUFFERED"] = "1"
42
+ env["FORCE_COLOR"] = "1"
43
+ env["CLICOLOR_FORCE"] = "1"
44
+
45
+ print(f"[PANOPTICON] Booting Immune System for: {' '.join(self.command)}")
46
+ print(
47
+ f"[PANOPTICON] Policies Active: Blacklist, AntiLoop (Fuzzy), AdversarialLogic"
48
+ )
49
+
50
+ # Flaw 2 Fix: Removing text=True to read raw binary bytes
51
+ self.process = subprocess.Popen(
52
+ self.command,
53
+ stdout=subprocess.PIPE,
54
+ stderr=subprocess.STDOUT,
55
+ stdin=subprocess.PIPE,
56
+ bufsize=0,
57
+ env=env,
58
+ )
59
+
60
+ t_out = threading.Thread(target=self._read_stdout)
61
+ t_out.daemon = True
62
+ t_out.start()
63
+
64
+ t_in = threading.Thread(target=self._forward_stdin)
65
+ t_in.daemon = True
66
+ t_in.start()
67
+
68
+ t_eval = threading.Thread(target=self._eval_loop)
69
+ t_eval.daemon = True
70
+ t_eval.start()
71
+
72
+ self.process.wait()
73
+ self.running = False
74
+
75
+ def _forward_stdin(self):
76
+ import select
77
+ try:
78
+ while self.running and self.process.poll() is None:
79
+ # Non-blocking check: only read if data is available
80
+ if sys.platform == "win32":
81
+ # Windows doesn't support select on stdin, so use a small sleep
82
+ time.sleep(0.1)
83
+ else:
84
+ # Unix/Linux: use select to check if stdin is readable
85
+ ready, _, _ = select.select([sys.stdin], [], [], 0.5)
86
+ if not ready:
87
+ continue
88
+
89
+ try:
90
+ line = sys.stdin.buffer.readline()
91
+ if not line: # EOF reached
92
+ break
93
+ self.process.stdin.write(line)
94
+ self.process.stdin.flush()
95
+ except (EOFError, OSError):
96
+ break
97
+ except Exception:
98
+ pass
99
+
100
+ def _read_stdout(self):
101
+ # Flaw 2 Fix: Safely decode multi-byte UTF-8 emojis incrementally
102
+ decoder = codecs.getincrementaldecoder("utf-8")(errors="replace")
103
+ try:
104
+ while self.running and self.process.poll() is None:
105
+ byte_chunk = self.process.stdout.read(1)
106
+ if not byte_chunk:
107
+ break
108
+
109
+ # Mirror raw bytes to actual terminal seamlessly
110
+ sys.stdout.buffer.write(byte_chunk)
111
+ sys.stdout.buffer.flush()
112
+
113
+ char_str = decoder.decode(byte_chunk)
114
+ if char_str:
115
+ with self.lock:
116
+ self.buffer.append(char_str)
117
+ except Exception:
118
+ pass
119
+
120
+ def _eval_loop(self):
121
+ while self.running and self.process.poll() is None:
122
+ time.sleep(15)
123
+
124
+ with self.lock:
125
+ if not self.buffer:
126
+ continue
127
+ full_text = "".join(self.buffer)
128
+
129
+ # Flaw 1 Fix: Sliding window overlap to prevent bridging blindness
130
+ overlap = full_text[-100:] if len(full_text) > 100 else ""
131
+ self.buffer.clear()
132
+ if overlap:
133
+ self.buffer.append(overlap)
134
+
135
+ clean_text = strip_ansi(full_text)
136
+ self.observer.log_action(
137
+ "CLI_Agent", clean_text, "cli_execution", len(clean_text) // 4
138
+ )
139
+
140
+ try:
141
+ self.observer._evaluate_state("CLI_Agent")
142
+ except InterventionException as e:
143
+ print(f"\n\n[PANOPTICON GUILLOTINE TRIGGERED]")
144
+ print(f"[REASON]: {e.args[0]}")
145
+ print(
146
+ f"[LIVE INJECTION]: Interrupting agent and injecting correction...\n"
147
+ )
148
+
149
+ # Flaw 1 Fix: The "Unkillable Zombie" SIGINT Interrupt
150
+ try:
151
+ if sys.platform != "win32":
152
+ self.process.send_signal(signal.SIGINT)
153
+ # Give the agent a split second to catch the interrupt before writing to stdin
154
+ time.sleep(0.5)
155
+ except Exception:
156
+ pass
157
+
158
+ try:
159
+ # Write the injection payload as binary
160
+ payload = (e.course_correction + "\n").encode("utf-8")
161
+ self.process.stdin.write(payload)
162
+ self.process.stdin.flush()
163
+ except Exception as ex:
164
+ print(
165
+ f"[ERROR] Live injection failed or agent deadlocked: {ex}. Hard terminating."
166
+ )
167
+ self.process.terminate()
168
+ self.running = False
169
+ break
170
+
171
+
172
+ def main():
173
+ try:
174
+ from dotenv import load_dotenv
175
+
176
+ load_dotenv()
177
+ except ImportError:
178
+ pass
179
+
180
+ parser = argparse.ArgumentParser(
181
+ description="Panopticon: Production-Grade Immune System for AI CLIs"
182
+ )
183
+ parser.add_argument(
184
+ "command", nargs=argparse.REMAINDER, help="The command to run, e.g., 'claude'"
185
+ )
186
+ args = parser.parse_args()
187
+
188
+ if not args.command:
189
+ print("Usage: panopticon [command]")
190
+ sys.exit(1)
191
+
192
+ wrapper = CLIWrapper(args.command)
193
+ wrapper.run()
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()