@neurodock/cli 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +198 -1
- package/README.md +155 -27
- package/dist/assets/hooks/neurodock_daemon.py +506 -0
- package/dist/assets/hooks/proactive_guardrail.py +520 -0
- package/dist/clients/claude-code.d.ts.map +1 -1
- package/dist/clients/claude-code.js +3 -1
- package/dist/clients/claude-code.js.map +1 -1
- package/dist/clients/claude-desktop.d.ts.map +1 -1
- package/dist/clients/claude-desktop.js +3 -1
- package/dist/clients/claude-desktop.js.map +1 -1
- package/dist/clients/cursor.d.ts.map +1 -1
- package/dist/clients/cursor.js +3 -1
- package/dist/clients/cursor.js.map +1 -1
- package/dist/clients/index.d.ts.map +1 -1
- package/dist/clients/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +59 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/examples.d.ts +28 -0
- package/dist/commands/examples.d.ts.map +1 -0
- package/dist/commands/examples.js +168 -0
- package/dist/commands/examples.js.map +1 -0
- package/dist/commands/host.d.ts.map +1 -1
- package/dist/commands/host.js +1 -1
- package/dist/commands/host.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-all.d.ts +78 -0
- package/dist/commands/install-all.d.ts.map +1 -0
- package/dist/commands/install-all.js +283 -0
- package/dist/commands/install-all.js.map +1 -0
- package/dist/commands/install-hooks.d.ts +20 -0
- package/dist/commands/install-hooks.d.ts.map +1 -0
- package/dist/commands/install-hooks.js +356 -0
- package/dist/commands/install-hooks.js.map +1 -0
- package/dist/commands/plugin.d.ts +122 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js +545 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/commands/profile.d.ts.map +1 -1
- package/dist/commands/profile.js +11 -2
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +179 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +11 -3
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +13 -22
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +11 -172
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +12 -3
- package/dist/commands/validate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/json-patch.d.ts.map +1 -1
- package/dist/lib/json-patch.js +1 -1
- package/dist/lib/json-patch.js.map +1 -1
- package/dist/lib/mcp-entries.d.ts.map +1 -1
- package/dist/lib/mcp-entries.js +25 -3
- package/dist/lib/mcp-entries.js.map +1 -1
- package/dist/lib/paths.d.ts +10 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +19 -2
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/plugin-schema.d.ts +12 -0
- package/dist/lib/plugin-schema.d.ts.map +1 -0
- package/dist/lib/plugin-schema.js +71 -0
- package/dist/lib/plugin-schema.js.map +1 -0
- package/dist/profile/defaults.d.ts.map +1 -1
- package/dist/profile/defaults.js +3 -1
- package/dist/profile/defaults.js.map +1 -1
- package/dist/profile/loader.d.ts.map +1 -1
- package/dist/profile/loader.js +3 -1
- package/dist/profile/loader.js.map +1 -1
- package/dist/profile/validator.d.ts.map +1 -1
- package/dist/profile/validator.js +5 -1
- package/dist/profile/validator.js.map +1 -1
- package/package.json +31 -14
- package/LICENSE +0 -11
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""NeuroDock proactive guardrail — Claude Code hook (self-contained).
|
|
3
|
+
|
|
4
|
+
Bundled with `@neurodock/cli`; copied to `~/.neurodock/hooks/` by
|
|
5
|
+
`neurodock install-hooks`. Pure stdlib — no pip install, no MCP server
|
|
6
|
+
required. Heuristics vendored from packages/mcp-guardrail so the hook
|
|
7
|
+
keeps working when MCP servers aren't running yet.
|
|
8
|
+
|
|
9
|
+
Hook events handled (Claude Code subcommand args):
|
|
10
|
+
|
|
11
|
+
session-start Track session start time + clock band; emit a banner
|
|
12
|
+
if we're in the deep-night band.
|
|
13
|
+
pre-tool Record the current user prompt; every Nth tool use,
|
|
14
|
+
evaluate hyperfocus + rumination and emit banners.
|
|
15
|
+
post-tool Detect sycophancy patterns in assistant responses.
|
|
16
|
+
stop Mark session end; clear in-flight state.
|
|
17
|
+
|
|
18
|
+
Wire-up in `~/.claude/settings.json`:
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
"hooks": {
|
|
22
|
+
"SessionStart": [
|
|
23
|
+
{"hooks": [{"type": "command", "command": "python ~/.neurodock/hooks/proactive_guardrail.py session-start"}]}
|
|
24
|
+
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{"hooks": [{"type": "command", "command": "python ~/.neurodock/hooks/proactive_guardrail.py pre-tool"}]}
|
|
27
|
+
],
|
|
28
|
+
"PostToolUse": [
|
|
29
|
+
{"hooks": [{"type": "command", "command": "python ~/.neurodock/hooks/proactive_guardrail.py post-tool"}]}
|
|
30
|
+
],
|
|
31
|
+
"Stop": [
|
|
32
|
+
{"hooks": [{"type": "command", "command": "python ~/.neurodock/hooks/proactive_guardrail.py stop"}]}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
`neurodock install-hooks` writes this idempotently.
|
|
38
|
+
|
|
39
|
+
Opt-out: set `NEURODOCK_GUARDRAILS=off` in the environment, or delete
|
|
40
|
+
the `hooks` entries from `settings.json`. The hook is silent by
|
|
41
|
+
default unless a heuristic actually trips.
|
|
42
|
+
|
|
43
|
+
State files (all under `~/.neurodock/state/`):
|
|
44
|
+
|
|
45
|
+
guardrail-session.json - {started_at, intent, tool_count}
|
|
46
|
+
guardrail-prompts.json - rolling list of {at, text} for rumination
|
|
47
|
+
guardrail-log.jsonl - audit trail (every banner emitted)
|
|
48
|
+
|
|
49
|
+
The hook NEVER blocks the user's work. Any internal error is logged
|
|
50
|
+
and swallowed.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import json
|
|
56
|
+
import os
|
|
57
|
+
import re
|
|
58
|
+
import sys
|
|
59
|
+
from dataclasses import dataclass
|
|
60
|
+
from datetime import datetime, time, timedelta, timezone
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
# ── Configuration ────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
VERSION = "0.0.1"
|
|
67
|
+
STATE_DIR = Path.home() / ".neurodock" / "state"
|
|
68
|
+
LOG_FILE = STATE_DIR / "guardrail-log.jsonl"
|
|
69
|
+
SESSION_FILE = STATE_DIR / "guardrail-session.json"
|
|
70
|
+
PROMPTS_FILE = STATE_DIR / "guardrail-prompts.json"
|
|
71
|
+
|
|
72
|
+
# Hyperfocus heuristic — mirrors packages/mcp-guardrail/heuristics/hyperfocus.py
|
|
73
|
+
HYPERFOCUS_BREAK_MINUTES_DEFAULT = 90
|
|
74
|
+
HYPERFOCUS_GENTLE_RATIO = 0.60 # 54 min
|
|
75
|
+
HYPERFOCUS_NUDGE_RATIO = 1.00 # 90 min
|
|
76
|
+
HYPERFOCUS_HARD_RATIO = 4.0 / 3.0 # 120 min
|
|
77
|
+
|
|
78
|
+
# Rumination heuristic — Jaccard similarity over normalised word sets.
|
|
79
|
+
RUMINATION_WINDOW_MINUTES_DEFAULT = 90
|
|
80
|
+
RUMINATION_THRESHOLD_DEFAULT = 3
|
|
81
|
+
RUMINATION_SIMILARITY_DEFAULT = 0.55 # tuned per mcp-guardrail tests
|
|
82
|
+
|
|
83
|
+
# Don't run heuristics on every single tool call — that 4x's the hook
|
|
84
|
+
# latency. Every Nth PreToolUse instead.
|
|
85
|
+
PRETOOL_CHECK_EVERY_N = 5
|
|
86
|
+
|
|
87
|
+
# Cap prompt-history file so it doesn't grow forever.
|
|
88
|
+
MAX_PROMPT_HISTORY = 200
|
|
89
|
+
|
|
90
|
+
# Deep-night / late-night clock bands trigger an early-warning banner
|
|
91
|
+
# at session-start. End-of-day defaults align with `profile.example.yaml`.
|
|
92
|
+
DEEP_NIGHT_HOURS = range(0, 6) # 00:00..05:59 local
|
|
93
|
+
LATE_NIGHT_HOURS = range(22, 24) # 22:00..23:59 local
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── Main dispatch ────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main() -> int:
|
|
100
|
+
if os.environ.get("NEURODOCK_GUARDRAILS", "").lower() == "off":
|
|
101
|
+
return 0
|
|
102
|
+
if len(sys.argv) < 2:
|
|
103
|
+
return 0
|
|
104
|
+
kind = sys.argv[1]
|
|
105
|
+
payload = _read_stdin_payload()
|
|
106
|
+
try:
|
|
107
|
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
except OSError:
|
|
109
|
+
return 0 # filesystem unavailable — fail silent
|
|
110
|
+
try:
|
|
111
|
+
if kind == "session-start":
|
|
112
|
+
_on_session_start(payload)
|
|
113
|
+
elif kind == "pre-tool":
|
|
114
|
+
_on_pre_tool(payload)
|
|
115
|
+
elif kind == "post-tool":
|
|
116
|
+
_on_post_tool(payload)
|
|
117
|
+
elif kind == "stop":
|
|
118
|
+
_on_stop(payload)
|
|
119
|
+
elif kind == "self-test":
|
|
120
|
+
return _self_test()
|
|
121
|
+
except Exception as exc: # noqa: BLE001 — must never block the user
|
|
122
|
+
_log("error", {"kind": kind, "error": str(exc)})
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ── Hook handlers ────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _on_session_start(_payload: dict[str, Any]) -> None:
|
|
130
|
+
now = _now()
|
|
131
|
+
state = _load_session()
|
|
132
|
+
state["started_at"] = now.isoformat()
|
|
133
|
+
state["tool_count"] = 0
|
|
134
|
+
_save_session(state)
|
|
135
|
+
band = _clock_band(now)
|
|
136
|
+
if band in ("deep_night", "late_night"):
|
|
137
|
+
_emit_banner(
|
|
138
|
+
f"NeuroDock: it's {band.replace('_', ' ')} local time. "
|
|
139
|
+
f"I'll nudge you toward stopping every "
|
|
140
|
+
f"{HYPERFOCUS_BREAK_MINUTES_DEFAULT} minutes."
|
|
141
|
+
)
|
|
142
|
+
_log("session-start", {"band": band})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _on_pre_tool(payload: dict[str, Any]) -> None:
|
|
146
|
+
state = _load_session()
|
|
147
|
+
state["tool_count"] = int(state.get("tool_count", 0)) + 1
|
|
148
|
+
_save_session(state)
|
|
149
|
+
|
|
150
|
+
prompt = _extract_user_prompt(payload)
|
|
151
|
+
if prompt:
|
|
152
|
+
_record_prompt(prompt)
|
|
153
|
+
|
|
154
|
+
if state["tool_count"] % PRETOOL_CHECK_EVERY_N != 0:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
hyperfocus_banner = _evaluate_hyperfocus(state)
|
|
158
|
+
if hyperfocus_banner:
|
|
159
|
+
_emit_banner(hyperfocus_banner)
|
|
160
|
+
rumination_banner = _evaluate_rumination()
|
|
161
|
+
if rumination_banner:
|
|
162
|
+
_emit_banner(rumination_banner)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _on_post_tool(payload: dict[str, Any]) -> None:
|
|
166
|
+
response = _extract_assistant_response(payload)
|
|
167
|
+
if not response:
|
|
168
|
+
return
|
|
169
|
+
sycophancy_banner = _evaluate_sycophancy(response)
|
|
170
|
+
if sycophancy_banner:
|
|
171
|
+
_emit_banner(sycophancy_banner)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _on_stop(_payload: dict[str, Any]) -> None:
|
|
175
|
+
state = _load_session()
|
|
176
|
+
started = state.get("started_at")
|
|
177
|
+
duration_min: int | None = None
|
|
178
|
+
if isinstance(started, str):
|
|
179
|
+
try:
|
|
180
|
+
elapsed = _now() - datetime.fromisoformat(started)
|
|
181
|
+
duration_min = int(elapsed.total_seconds() // 60)
|
|
182
|
+
except ValueError:
|
|
183
|
+
pass
|
|
184
|
+
_save_session({}) # clear
|
|
185
|
+
_log("session-end", {"duration_min": duration_min})
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── Heuristics (vendored from packages/mcp-guardrail) ────────────────────
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _evaluate_hyperfocus(state: dict[str, Any]) -> str | None:
|
|
192
|
+
"""Elapsed-threshold heuristic; mirrors mcp-guardrail's structure."""
|
|
193
|
+
started_iso = state.get("started_at")
|
|
194
|
+
if not isinstance(started_iso, str):
|
|
195
|
+
return None
|
|
196
|
+
try:
|
|
197
|
+
started = datetime.fromisoformat(started_iso)
|
|
198
|
+
except ValueError:
|
|
199
|
+
return None
|
|
200
|
+
now = _now()
|
|
201
|
+
elapsed = now - started
|
|
202
|
+
elapsed_min = elapsed.total_seconds() / 60.0
|
|
203
|
+
|
|
204
|
+
gentle = HYPERFOCUS_BREAK_MINUTES_DEFAULT * HYPERFOCUS_GENTLE_RATIO
|
|
205
|
+
nudge = HYPERFOCUS_BREAK_MINUTES_DEFAULT * HYPERFOCUS_NUDGE_RATIO
|
|
206
|
+
hard = HYPERFOCUS_BREAK_MINUTES_DEFAULT * HYPERFOCUS_HARD_RATIO
|
|
207
|
+
|
|
208
|
+
band = _clock_band(now)
|
|
209
|
+
past_eod = band in ("late_night", "deep_night")
|
|
210
|
+
|
|
211
|
+
level: str
|
|
212
|
+
if elapsed_min < gentle:
|
|
213
|
+
level = "none"
|
|
214
|
+
elif elapsed_min < nudge:
|
|
215
|
+
level = "gentle"
|
|
216
|
+
elif elapsed_min < hard:
|
|
217
|
+
level = "nudge"
|
|
218
|
+
else:
|
|
219
|
+
level = "hard"
|
|
220
|
+
# End-of-day escalates one rung.
|
|
221
|
+
if past_eod and level == "gentle":
|
|
222
|
+
level = "nudge"
|
|
223
|
+
elif past_eod and level == "nudge":
|
|
224
|
+
level = "hard"
|
|
225
|
+
|
|
226
|
+
if level == "none":
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
elapsed_label = f"{int(elapsed_min)} min"
|
|
230
|
+
if level == "gentle":
|
|
231
|
+
return (
|
|
232
|
+
f"NeuroDock hyperfocus check ({elapsed_label}): consider standing up, "
|
|
233
|
+
"hydrating, looking 20ft away for 20 seconds."
|
|
234
|
+
)
|
|
235
|
+
if level == "nudge":
|
|
236
|
+
return (
|
|
237
|
+
f"NeuroDock hyperfocus check ({elapsed_label}): worth taking a real "
|
|
238
|
+
"break — walk outside, switch context for 10 minutes."
|
|
239
|
+
)
|
|
240
|
+
return (
|
|
241
|
+
f"NeuroDock hyperfocus check ({elapsed_label}): you've crossed the "
|
|
242
|
+
"hard threshold. Save your work and stop for the day."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _evaluate_rumination() -> str | None:
|
|
247
|
+
"""Jaccard-similarity rumination detector across recent prompts."""
|
|
248
|
+
prompts = _load_prompts()
|
|
249
|
+
if len(prompts) < RUMINATION_THRESHOLD_DEFAULT:
|
|
250
|
+
return None
|
|
251
|
+
window_start = _now() - timedelta(minutes=RUMINATION_WINDOW_MINUTES_DEFAULT)
|
|
252
|
+
recent = [
|
|
253
|
+
p for p in prompts
|
|
254
|
+
if _parse_iso(p.get("at", "")) >= window_start
|
|
255
|
+
]
|
|
256
|
+
if len(recent) < RUMINATION_THRESHOLD_DEFAULT:
|
|
257
|
+
return None
|
|
258
|
+
# Compare the latest prompt to the others.
|
|
259
|
+
latest = recent[-1]["text"]
|
|
260
|
+
matches = 0
|
|
261
|
+
for prior in recent[:-1]:
|
|
262
|
+
sim = _jaccard_similarity(latest, prior["text"])
|
|
263
|
+
if sim >= RUMINATION_SIMILARITY_DEFAULT:
|
|
264
|
+
matches += 1
|
|
265
|
+
if matches < RUMINATION_THRESHOLD_DEFAULT - 1:
|
|
266
|
+
return None
|
|
267
|
+
return (
|
|
268
|
+
f"NeuroDock rumination check: you've asked a variant of this question "
|
|
269
|
+
f"{matches + 1} times in the last {RUMINATION_WINDOW_MINUTES_DEFAULT} "
|
|
270
|
+
"minutes. Want to step back, or are you finding what you need?"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _evaluate_sycophancy(response: str) -> str | None:
|
|
275
|
+
"""Lightweight sycophancy detector — opens with absolute agreement +
|
|
276
|
+
no trade-off named. Conservative; designed to fire rarely."""
|
|
277
|
+
if len(response) < 60:
|
|
278
|
+
return None
|
|
279
|
+
opener = response.lstrip()[:200].lower()
|
|
280
|
+
absolutes = [
|
|
281
|
+
"absolutely", "exactly right", "you're right",
|
|
282
|
+
"you are right", "great point", "excellent point",
|
|
283
|
+
"perfect", "spot on", "100%", "100 percent",
|
|
284
|
+
]
|
|
285
|
+
hits = [phrase for phrase in absolutes if phrase in opener]
|
|
286
|
+
if not hits:
|
|
287
|
+
return None
|
|
288
|
+
# If the response contains a trade-off marker, treat it as balanced.
|
|
289
|
+
tradeoff_markers = [
|
|
290
|
+
"however", "trade-off", "tradeoff", "downside",
|
|
291
|
+
"but ", "although", "the cost is", "the risk is",
|
|
292
|
+
]
|
|
293
|
+
if any(marker in response.lower() for marker in tradeoff_markers):
|
|
294
|
+
return None
|
|
295
|
+
return (
|
|
296
|
+
"NeuroDock sycophancy check: Claude's response opens with "
|
|
297
|
+
f"'{hits[0]}' and names no trade-off. Push back if you disagree."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _jaccard_similarity(a: str, b: str) -> float:
|
|
302
|
+
set_a = _normalise_for_similarity(a)
|
|
303
|
+
set_b = _normalise_for_similarity(b)
|
|
304
|
+
if not set_a or not set_b:
|
|
305
|
+
return 0.0
|
|
306
|
+
intersection = len(set_a & set_b)
|
|
307
|
+
union = len(set_a | set_b)
|
|
308
|
+
return intersection / union if union > 0 else 0.0
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
_STOP_WORDS = frozenset({
|
|
312
|
+
"the", "a", "an", "and", "or", "but", "is", "are", "was", "were",
|
|
313
|
+
"be", "been", "being", "have", "has", "had", "do", "does", "did",
|
|
314
|
+
"will", "would", "could", "should", "may", "might", "must",
|
|
315
|
+
"shall", "can", "of", "in", "on", "at", "to", "for", "with",
|
|
316
|
+
"by", "from", "as", "if", "then", "else", "when", "where", "why",
|
|
317
|
+
"how", "what", "which", "who", "this", "that", "these", "those",
|
|
318
|
+
"i", "you", "he", "she", "it", "we", "they", "me", "him", "her",
|
|
319
|
+
"us", "them", "my", "your", "his", "its", "our", "their",
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _normalise_for_similarity(text: str) -> set[str]:
|
|
324
|
+
tokens = re.findall(r"[a-z0-9]+", text.lower())
|
|
325
|
+
return {t for t in tokens if len(t) > 2 and t not in _STOP_WORDS}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ── Clock bands ──────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _clock_band(now: datetime) -> str:
|
|
332
|
+
hour = now.hour
|
|
333
|
+
if hour in DEEP_NIGHT_HOURS:
|
|
334
|
+
return "deep_night"
|
|
335
|
+
if hour in LATE_NIGHT_HOURS:
|
|
336
|
+
return "late_night"
|
|
337
|
+
if hour < 12:
|
|
338
|
+
return "morning"
|
|
339
|
+
if hour < 17:
|
|
340
|
+
return "afternoon"
|
|
341
|
+
return "evening"
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _now() -> datetime:
|
|
345
|
+
return datetime.now(timezone.utc).astimezone()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ── Payload extraction (best-effort against Claude Code shape) ───────────
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _extract_user_prompt(payload: dict[str, Any]) -> str | None:
|
|
352
|
+
"""Pull a user prompt from the hook payload. Claude Code's exact field
|
|
353
|
+
name varies by event — try a handful and stop on first match."""
|
|
354
|
+
for key in ("prompt", "user_prompt", "userPrompt", "input"):
|
|
355
|
+
value = payload.get(key)
|
|
356
|
+
if isinstance(value, str) and value.strip():
|
|
357
|
+
return value.strip()
|
|
358
|
+
# Tool input shape — used by some hook variants
|
|
359
|
+
tool_input = payload.get("tool_input")
|
|
360
|
+
if isinstance(tool_input, dict):
|
|
361
|
+
text = tool_input.get("prompt") or tool_input.get("input")
|
|
362
|
+
if isinstance(text, str) and text.strip():
|
|
363
|
+
return text.strip()
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _extract_assistant_response(payload: dict[str, Any]) -> str | None:
|
|
368
|
+
"""Pull an assistant response from PostToolUse payload."""
|
|
369
|
+
for key in ("response", "response_text", "output", "result"):
|
|
370
|
+
value = payload.get(key)
|
|
371
|
+
if isinstance(value, str) and value.strip():
|
|
372
|
+
return value.strip()
|
|
373
|
+
tool_response = payload.get("tool_response")
|
|
374
|
+
if isinstance(tool_response, dict):
|
|
375
|
+
text = tool_response.get("text") or tool_response.get("output")
|
|
376
|
+
if isinstance(text, str) and text.strip():
|
|
377
|
+
return text.strip()
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ── State I/O ────────────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _load_session() -> dict[str, Any]:
|
|
385
|
+
try:
|
|
386
|
+
with SESSION_FILE.open("r", encoding="utf-8") as fh:
|
|
387
|
+
data = json.load(fh)
|
|
388
|
+
return data if isinstance(data, dict) else {}
|
|
389
|
+
except (OSError, json.JSONDecodeError):
|
|
390
|
+
return {}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _save_session(state: dict[str, Any]) -> None:
|
|
394
|
+
try:
|
|
395
|
+
with SESSION_FILE.open("w", encoding="utf-8") as fh:
|
|
396
|
+
json.dump(state, fh)
|
|
397
|
+
except OSError:
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _load_prompts() -> list[dict[str, Any]]:
|
|
402
|
+
try:
|
|
403
|
+
with PROMPTS_FILE.open("r", encoding="utf-8") as fh:
|
|
404
|
+
data = json.load(fh)
|
|
405
|
+
return data if isinstance(data, list) else []
|
|
406
|
+
except (OSError, json.JSONDecodeError):
|
|
407
|
+
return []
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _record_prompt(text: str) -> None:
|
|
411
|
+
prompts = _load_prompts()
|
|
412
|
+
prompts.append({"at": _now().isoformat(), "text": text[:2000]})
|
|
413
|
+
if len(prompts) > MAX_PROMPT_HISTORY:
|
|
414
|
+
prompts = prompts[-MAX_PROMPT_HISTORY:]
|
|
415
|
+
try:
|
|
416
|
+
with PROMPTS_FILE.open("w", encoding="utf-8") as fh:
|
|
417
|
+
json.dump(prompts, fh)
|
|
418
|
+
except OSError:
|
|
419
|
+
pass
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _parse_iso(value: str) -> datetime:
|
|
423
|
+
try:
|
|
424
|
+
return datetime.fromisoformat(value)
|
|
425
|
+
except ValueError:
|
|
426
|
+
return datetime.min.replace(tzinfo=timezone.utc)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# ── Output ───────────────────────────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _emit_banner(message: str) -> None:
|
|
433
|
+
line = f"\n┌─ NeuroDock ──\n│ {message}\n└──\n"
|
|
434
|
+
sys.stderr.write(line)
|
|
435
|
+
sys.stderr.flush()
|
|
436
|
+
_log("banner", {"message": message[:200]})
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _log(event: str, data: dict[str, Any]) -> None:
|
|
440
|
+
try:
|
|
441
|
+
entry = {
|
|
442
|
+
"at": _now().isoformat(),
|
|
443
|
+
"event": event,
|
|
444
|
+
"version": VERSION,
|
|
445
|
+
**data,
|
|
446
|
+
}
|
|
447
|
+
with LOG_FILE.open("a", encoding="utf-8") as fh:
|
|
448
|
+
fh.write(json.dumps(entry) + "\n")
|
|
449
|
+
except OSError:
|
|
450
|
+
pass
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _read_stdin_payload() -> dict[str, Any]:
|
|
454
|
+
if sys.stdin.isatty():
|
|
455
|
+
return {}
|
|
456
|
+
try:
|
|
457
|
+
raw = sys.stdin.read()
|
|
458
|
+
if not raw.strip():
|
|
459
|
+
return {}
|
|
460
|
+
parsed = json.loads(raw)
|
|
461
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
462
|
+
except (json.JSONDecodeError, OSError):
|
|
463
|
+
return {}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# ── Self-test (run with `python proactive_guardrail.py self-test`) ──────
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _self_test() -> int:
|
|
470
|
+
"""Smoke-test each heuristic against a known-trip and known-skip input."""
|
|
471
|
+
ok = True
|
|
472
|
+
|
|
473
|
+
# Hyperfocus: 200 min elapsed → hard level
|
|
474
|
+
fake_state = {
|
|
475
|
+
"started_at": (_now() - timedelta(minutes=200)).isoformat(),
|
|
476
|
+
"tool_count": 5,
|
|
477
|
+
}
|
|
478
|
+
banner = _evaluate_hyperfocus(fake_state)
|
|
479
|
+
if banner is None or "hard" not in banner.lower():
|
|
480
|
+
sys.stderr.write(f"FAIL: hyperfocus hard-level: {banner!r}\n")
|
|
481
|
+
ok = False
|
|
482
|
+
|
|
483
|
+
# Hyperfocus: 10 min elapsed → no banner
|
|
484
|
+
fake_state["started_at"] = (_now() - timedelta(minutes=10)).isoformat()
|
|
485
|
+
if _evaluate_hyperfocus(fake_state) is not None:
|
|
486
|
+
sys.stderr.write("FAIL: hyperfocus should not fire at 10 min\n")
|
|
487
|
+
ok = False
|
|
488
|
+
|
|
489
|
+
# Sycophancy: positive case
|
|
490
|
+
sycophancy_text = "Absolutely! You're 100% right about this approach."
|
|
491
|
+
if _evaluate_sycophancy(sycophancy_text * 3) is None:
|
|
492
|
+
sys.stderr.write("FAIL: sycophancy should fire on pure agreement\n")
|
|
493
|
+
ok = False
|
|
494
|
+
|
|
495
|
+
# Sycophancy: balanced case
|
|
496
|
+
balanced_text = (
|
|
497
|
+
"Absolutely a valid approach, however the downside is increased "
|
|
498
|
+
"complexity and the trade-off is worth weighing carefully."
|
|
499
|
+
)
|
|
500
|
+
if _evaluate_sycophancy(balanced_text * 2) is not None:
|
|
501
|
+
sys.stderr.write("FAIL: sycophancy should NOT fire on balanced text\n")
|
|
502
|
+
ok = False
|
|
503
|
+
|
|
504
|
+
# Jaccard similarity sanity check
|
|
505
|
+
sim = _jaccard_similarity(
|
|
506
|
+
"how do I fix the linkedin image translation",
|
|
507
|
+
"the linkedin image translation is broken how do I fix it",
|
|
508
|
+
)
|
|
509
|
+
if sim < 0.5:
|
|
510
|
+
sys.stderr.write(f"FAIL: jaccard sim too low: {sim}\n")
|
|
511
|
+
ok = False
|
|
512
|
+
|
|
513
|
+
if ok:
|
|
514
|
+
sys.stdout.write(f"OK: proactive_guardrail v{VERSION} self-test passed.\n")
|
|
515
|
+
return 0
|
|
516
|
+
return 1
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
if __name__ == "__main__":
|
|
520
|
+
sys.exit(main())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/clients/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/clients/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,iBAAiB,EAAE,aAkB/B,CAAC"}
|
|
@@ -11,7 +11,9 @@ export const claudeCodeAdapter = {
|
|
|
11
11
|
},
|
|
12
12
|
shapeConfig(existing, mcpServers) {
|
|
13
13
|
const base = isObject(existing) ? { ...existing } : {};
|
|
14
|
-
const prev = isObject(base["mcpServers"])
|
|
14
|
+
const prev = isObject(base["mcpServers"])
|
|
15
|
+
? base["mcpServers"]
|
|
16
|
+
: {};
|
|
15
17
|
base["mcpServers"] = { ...prev, ...mcpServers };
|
|
16
18
|
return base;
|
|
17
19
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/clients/claude-code.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,CAAC,MAAM,iBAAiB,GAAkB;IAC9C,EAAE,EAAE,aAAa;IACjB,WAAW,EAAE,aAAa;IAC1B,WAAW,CAAC,GAAG;QACb,MAAM,KAAK,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO;YACL,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;YACzC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;SACpC,CAAC;IACJ,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/clients/claude-code.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,CAAC,MAAM,iBAAiB,GAAkB;IAC9C,EAAE,EAAE,aAAa;IACjB,WAAW,EAAE,aAAa;IAC1B,WAAW,CAAC,GAAG;QACb,MAAM,KAAK,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO;YACL,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;YACzC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;SACpC,CAAC;IACJ,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC,CAAE,IAAI,CAAC,YAAY,CAAoC;YACxD,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/clients/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/clients/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,oBAAoB,EAAE,aAclC,CAAC"}
|
|
@@ -7,7 +7,9 @@ export const claudeDesktopAdapter = {
|
|
|
7
7
|
},
|
|
8
8
|
shapeConfig(existing, mcpServers) {
|
|
9
9
|
const base = isObject(existing) ? { ...existing } : {};
|
|
10
|
-
const prev = isObject(base["mcpServers"])
|
|
10
|
+
const prev = isObject(base["mcpServers"])
|
|
11
|
+
? base["mcpServers"]
|
|
12
|
+
: {};
|
|
11
13
|
base["mcpServers"] = { ...prev, ...mcpServers };
|
|
12
14
|
return base;
|
|
13
15
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-desktop.js","sourceRoot":"","sources":["../../src/clients/claude-desktop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,CAAC,MAAM,oBAAoB,GAAkB;IACjD,EAAE,EAAE,gBAAgB;IACpB,WAAW,EAAE,gBAAgB;IAC7B,WAAW,CAAC,GAAG;QACb,OAAO,CAAC,EAAE,IAAI,EAAE,uBAAuB,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"claude-desktop.js","sourceRoot":"","sources":["../../src/clients/claude-desktop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,CAAC,MAAM,oBAAoB,GAAkB;IACjD,EAAE,EAAE,gBAAgB;IACpB,WAAW,EAAE,gBAAgB;IAC7B,WAAW,CAAC,GAAG;QACb,OAAO,CAAC,EAAE,IAAI,EAAE,uBAAuB,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC,CAAE,IAAI,CAAC,YAAY,CAAoC;YACxD,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/clients/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/clients/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,eAAO,MAAM,aAAa,EAAE,aAkB3B,CAAC"}
|
package/dist/clients/cursor.js
CHANGED
|
@@ -11,7 +11,9 @@ export const cursorAdapter = {
|
|
|
11
11
|
},
|
|
12
12
|
shapeConfig(existing, mcpServers) {
|
|
13
13
|
const base = isObject(existing) ? { ...existing } : {};
|
|
14
|
-
const prev = isObject(base["mcpServers"])
|
|
14
|
+
const prev = isObject(base["mcpServers"])
|
|
15
|
+
? base["mcpServers"]
|
|
16
|
+
: {};
|
|
15
17
|
base["mcpServers"] = { ...prev, ...mcpServers };
|
|
16
18
|
return base;
|
|
17
19
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/clients/cursor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,EAAE,EAAE,QAAQ;IACZ,WAAW,EAAE,QAAQ;IACrB,WAAW,CAAC,GAAG;QACb,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO;YACL,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;YACzC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;SACpC,CAAC;IACJ,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/clients/cursor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,EAAE,EAAE,QAAQ;IACZ,WAAW,EAAE,QAAQ;IACrB,WAAW,CAAC,GAAG;QACb,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO;YACL,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;YACzC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;SACpC,CAAC;IACJ,CAAC;IACD,WAAW,CAAC,QAAQ,EAAE,UAAU;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC,CAAE,IAAI,CAAC,YAAY,CAAoC;YACxD,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAKjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC;IAC9D,iEAAiE;IACjE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAKjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC;IAC9D,iEAAiE;IACjE,WAAW,CACT,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACzC,OAAO,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,aAAa,CAIpD,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,aAAa,CAMtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/clients/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmB5C,MAAM,CAAC,MAAM,WAAW,GAAiC;IACvD,oBAAoB;IACpB,iBAAiB;IACjB,aAAa;CACd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,EAAY;IACrC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAqFvD"}
|